aboutsummaryrefslogtreecommitdiff
path: root/pkg/blockchain/chainwriter
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/blockchain/chainwriter')
-rw-r--r--pkg/blockchain/chainwriter/chainwriter.go127
-rw-r--r--pkg/blockchain/chainwriter/config.go23
-rw-r--r--pkg/blockchain/chainwriter/fileinfo.go8
-rw-r--r--pkg/blockchain/chainwriter/readwrite.go41
-rw-r--r--pkg/blockchain/chainwriter/undoblock.go60
5 files changed, 259 insertions, 0 deletions
diff --git a/pkg/blockchain/chainwriter/chainwriter.go b/pkg/blockchain/chainwriter/chainwriter.go
new file mode 100644
index 0000000..67a7d49
--- /dev/null
+++ b/pkg/blockchain/chainwriter/chainwriter.go
@@ -0,0 +1,127 @@
+package chainwriter
+
+import (
+ "Chain/pkg/block"
+ "Chain/pkg/blockchain/blockinfodatabase"
+ "Chain/pkg/pro"
+ "Chain/pkg/utils"
+ "google.golang.org/protobuf/proto"
+ "log"
+ "os"
+)
+
+// ChainWriter handles all I/O for the BlockChain. It stores and retrieves
+// Blocks and UndoBlocks.
+// See config.go for more information on its fields.
+// Block files are of the format:
+// "DataDirectory/BlockFileName_CurrentBlockFileNumber.FileExtension"
+// Ex: "data/block_0.txt"
+// UndoBlock files are of the format:
+// "DataDirectory/UndoFileName_CurrentUndoFileNumber.FileExtension"
+// Ex: "data/undo_0.txt"
+type ChainWriter struct {
+ // data storage information
+ FileExtension string
+ DataDirectory string
+
+ // block information
+ BlockFileName string
+ CurrentBlockFileNumber uint32
+ CurrentBlockOffset uint32
+ MaxBlockFileSize uint32
+
+ // undo block information
+ UndoFileName string
+ CurrentUndoFileNumber uint32
+ CurrentUndoOffset uint32
+ MaxUndoFileSize uint32
+}
+
+// New returns a ChainWriter given a Config.
+func New(config *Config) *ChainWriter {
+ if err := os.MkdirAll(config.DataDirectory, 0700); err != nil {
+ log.Fatalf("Could not create ChainWriter's data directory")
+ }
+ return &ChainWriter{
+ FileExtension: config.FileExtension,
+ DataDirectory: config.DataDirectory,
+ BlockFileName: config.BlockFileName,
+ CurrentBlockFileNumber: 0,
+ CurrentBlockOffset: 0,
+ MaxBlockFileSize: config.MaxBlockFileSize,
+ UndoFileName: config.UndoFileName,
+ CurrentUndoFileNumber: 0,
+ CurrentUndoOffset: 0,
+ MaxUndoFileSize: config.MaxUndoFileSize,
+ }
+}
+
+// StoreBlock stores a Block and its corresponding UndoBlock to Disk,
+// returning a BlockRecord that contains information for later retrieval.
+func (cw *ChainWriter) StoreBlock(bl *block.Block, undoBlock *UndoBlock, height uint32) *blockinfodatabase.BlockRecord {
+ // serialize block
+ b := block.EncodeBlock(bl)
+ serializedBlock, err := proto.Marshal(b)
+ if err != nil {
+ utils.Debug.Printf("Failed to marshal block")
+ }
+ // serialize undo block
+ ub := EncodeUndoBlock(undoBlock)
+ serializedUndoBlock, err := proto.Marshal(ub)
+ if err != nil {
+ utils.Debug.Printf("Failed to marshal undo block")
+ }
+ // write block to disk
+ bfi := cw.WriteBlock(serializedBlock)
+ // create an empty file info, which we will update if the function is passed an undo block.
+ ufi := &FileInfo{}
+ if undoBlock.Amounts != nil {
+ ufi = cw.WriteUndoBlock(serializedUndoBlock)
+ }
+
+ return &blockinfodatabase.BlockRecord{
+ Header: bl.Header,
+ Height: height,
+ NumberOfTransactions: uint32(len(bl.Transactions)),
+ BlockFile: bfi.FileName,
+ BlockStartOffset: bfi.StartOffset,
+ BlockEndOffset: bfi.EndOffset,
+ UndoFile: ufi.FileName,
+ UndoStartOffset: ufi.StartOffset,
+ UndoEndOffset: ufi.EndOffset,
+ }
+}
+
+// WriteBlock writes a serialized Block to Disk and returns
+// a FileInfo for storage information.
+func (cw *ChainWriter) WriteBlock(serializedBlock []byte) *FileInfo {
+ //TODO
+ return nil
+}
+
+// WriteUndoBlock writes a serialized UndoBlock to Disk and returns
+// a FileInfo for storage information.
+func (cw *ChainWriter) WriteUndoBlock(serializedUndoBlock []byte) *FileInfo {
+ //TODO
+ return nil
+}
+
+// ReadBlock returns a Block given a FileInfo.
+func (cw *ChainWriter) ReadBlock(fi *FileInfo) *block.Block {
+ bytes := readFromDisk(fi)
+ pb := &pro.Block{}
+ if err := proto.Unmarshal(bytes, pb); err != nil {
+ utils.Debug.Printf("failed to unmarshal block from file info {%v}", fi)
+ }
+ return block.DecodeBlock(pb)
+}
+
+// ReadUndoBlock returns an UndoBlock given a FileInfo.
+func (cw *ChainWriter) ReadUndoBlock(fi *FileInfo) *UndoBlock {
+ bytes := readFromDisk(fi)
+ pub := &pro.UndoBlock{}
+ if err := proto.Unmarshal(bytes, pub); err != nil {
+ utils.Debug.Printf("failed to unmarshal undo block from file info {%v}", fi)
+ }
+ return DecodeUndoBlock(pub)
+}
diff --git a/pkg/blockchain/chainwriter/config.go b/pkg/blockchain/chainwriter/config.go
new file mode 100644
index 0000000..e217f7a
--- /dev/null
+++ b/pkg/blockchain/chainwriter/config.go
@@ -0,0 +1,23 @@
+package chainwriter
+
+// Config is the ChainWriter's configuration options.
+type Config struct {
+ FileExtension string
+ DataDirectory string
+ BlockFileName string
+ UndoFileName string
+ MaxBlockFileSize uint32
+ MaxUndoFileSize uint32
+}
+
+// DefaultConfig returns the default Config for the ChainWriter.
+func DefaultConfig() *Config {
+ return &Config{
+ FileExtension: ".txt",
+ DataDirectory: "data",
+ BlockFileName: "block",
+ UndoFileName: "undo",
+ MaxBlockFileSize: 1024,
+ MaxUndoFileSize: 1024,
+ }
+}
diff --git a/pkg/blockchain/chainwriter/fileinfo.go b/pkg/blockchain/chainwriter/fileinfo.go
new file mode 100644
index 0000000..95987b0
--- /dev/null
+++ b/pkg/blockchain/chainwriter/fileinfo.go
@@ -0,0 +1,8 @@
+package chainwriter
+
+// FileInfo determines where a Block or UndoBlock is stored.
+type FileInfo struct {
+ FileName string
+ StartOffset uint32
+ EndOffset uint32
+}
diff --git a/pkg/blockchain/chainwriter/readwrite.go b/pkg/blockchain/chainwriter/readwrite.go
new file mode 100644
index 0000000..32944a4
--- /dev/null
+++ b/pkg/blockchain/chainwriter/readwrite.go
@@ -0,0 +1,41 @@
+package chainwriter
+
+import (
+ "log"
+ "os"
+)
+
+// writeToDisk appends a slice of bytes to a file.
+func writeToDisk(fileName string, data []byte) {
+ file, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ if err != nil {
+ log.Fatalf("Unable to open file {%v}", fileName)
+ }
+ if _, err := file.Write(data); err != nil {
+ file.Close() // ignore error; Write error takes precedence
+ log.Fatalf("Failed to write to file {%v}", fileName)
+ }
+ if err := file.Close(); err != nil {
+ log.Fatalf("Failed to close file {%v}", fileName)
+ }
+}
+
+// readFromDisk return a slice of bytes from a file, given a FileInfo.
+func readFromDisk(info *FileInfo) []byte {
+ file, err := os.Open(info.FileName)
+ if err != nil {
+ log.Fatalf("Unable to open file {%v}", info.FileName)
+ }
+ if _, err2 := file.Seek(int64(info.StartOffset), 0); err2 != nil {
+ log.Fatalf("Failed to seek to {%v} in file {%v}", info.StartOffset, info.FileName)
+ }
+ numBytes := info.EndOffset - info.StartOffset
+ buf := make([]byte, numBytes)
+ if n, err3 := file.Read(buf); uint32(n) != info.EndOffset-info.StartOffset || err3 != nil {
+ log.Fatalf("Failed to read {%v} bytes from file {%v}", numBytes, info.FileName)
+ }
+ if err4 := file.Close(); err4 != nil {
+ log.Fatalf("Failed to close file {%v}", info.FileName)
+ }
+ return buf
+}
diff --git a/pkg/blockchain/chainwriter/undoblock.go b/pkg/blockchain/chainwriter/undoblock.go
new file mode 100644
index 0000000..5827f57
--- /dev/null
+++ b/pkg/blockchain/chainwriter/undoblock.go
@@ -0,0 +1,60 @@
+package chainwriter
+
+import "Chain/pkg/pro"
+
+// UndoBlock is used to reverse the side effects causes by a Block.
+// When the chain reverts a block's Transactions, it must both (1)
+// remove newly created TransactionOutputs and (2) convert
+// TransactionInputs back into available TransactionOutputs.
+// This struct helps with (2).
+// TransactionInputHashes are the hashes of the TransactionInputs that
+// the UndoBlock must revert.
+// OutputIndexes are the OutputIndexes of the TransactionInputs.
+// Amounts are the amounts of the parent TransactionOutputs.
+// LockingScripts are the locking scripts of the parent TransactionOutputs.
+type UndoBlock struct {
+ TransactionInputHashes []string
+ OutputIndexes []uint32
+ Amounts []uint32
+ LockingScripts []string
+}
+
+// EncodeUndoBlock returns a pro.UndoBlock given an UndoBlock.
+func EncodeUndoBlock(ub *UndoBlock) *pro.UndoBlock {
+ var transactionInputHashes []string
+ var outputIndexes []uint32
+ var amounts []uint32
+ var lockingScripts []string
+ for i := 0; i < len(ub.TransactionInputHashes); i++ {
+ transactionInputHashes = append(transactionInputHashes, ub.TransactionInputHashes[i])
+ outputIndexes = append(outputIndexes, ub.OutputIndexes[i])
+ amounts = append(amounts, ub.Amounts[i])
+ lockingScripts = append(lockingScripts, ub.LockingScripts[i])
+ }
+ return &pro.UndoBlock{
+ TransactionInputHashes: transactionInputHashes,
+ OutputIndexes: outputIndexes,
+ Amounts: amounts,
+ LockingScripts: lockingScripts,
+ }
+}
+
+// DecodeUndoBlock returns an UndoBlock given a pro.UndoBlock
+func DecodeUndoBlock(pub *pro.UndoBlock) *UndoBlock {
+ var transactionInputHashes []string
+ var outputIndexes []uint32
+ var amounts []uint32
+ var lockingScripts []string
+ for i := 0; i < len(pub.GetTransactionInputHashes()); i++ {
+ transactionInputHashes = append(transactionInputHashes, pub.GetTransactionInputHashes()[i])
+ outputIndexes = append(outputIndexes, pub.GetOutputIndexes()[i])
+ amounts = append(amounts, pub.GetAmounts()[i])
+ lockingScripts = append(lockingScripts, pub.GetLockingScripts()[i])
+ }
+ return &UndoBlock{
+ TransactionInputHashes: transactionInputHashes,
+ OutputIndexes: outputIndexes,
+ Amounts: amounts,
+ LockingScripts: lockingScripts,
+ }
+}