diff options
Diffstat (limited to 'pkg/blockchain/blockchain.go')
-rw-r--r-- | pkg/blockchain/blockchain.go | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/pkg/blockchain/blockchain.go b/pkg/blockchain/blockchain.go new file mode 100644 index 0000000..c456be1 --- /dev/null +++ b/pkg/blockchain/blockchain.go @@ -0,0 +1,261 @@ +package blockchain + +import ( + "Chain/pkg/block" + "Chain/pkg/blockchain/blockinfodatabase" + "Chain/pkg/blockchain/chainwriter" + "Chain/pkg/blockchain/coindatabase" + "Chain/pkg/utils" +) + +// BlockChain is the main type of this project. +// Length is the length of the active chain. +// LastBlock is the last block of the active chain. +// LastHash is the hash of the last block of the active chain. +// UnsafeHashes are the hashes of the "unsafe" blocks on the +// active chain. These "unsafe" blocks may be reverted during a +// fork. +// maxHashes is the number of unsafe hashes that the chain keeps track of. +// BlockInfoDB is a pointer to a block info database +// ChainWriter is a pointer to a chain writer. +// CoinDB is a pointer to a coin database. +type BlockChain struct { + Length uint32 + LastBlock *block.Block + LastHash string + UnsafeHashes []string + maxHashes int + + BlockInfoDB *blockinfodatabase.BlockInfoDatabase + ChainWriter *chainwriter.ChainWriter + CoinDB *coindatabase.CoinDatabase +} + +// New returns a blockchain given a Config. +func New(config *Config) *BlockChain { + genBlock := GenesisBlock(config) + hash := genBlock.Hash() + bc := &BlockChain{ + Length: 1, + LastBlock: genBlock, + LastHash: hash, + UnsafeHashes: []string{}, + maxHashes: 6, + BlockInfoDB: blockinfodatabase.New(blockinfodatabase.DefaultConfig()), + ChainWriter: chainwriter.New(chainwriter.DefaultConfig()), + CoinDB: coindatabase.New(coindatabase.DefaultConfig()), + } + // have to store the genesis block + bc.CoinDB.StoreBlock(genBlock.Transactions, true) + ub := &chainwriter.UndoBlock{} + br := bc.ChainWriter.StoreBlock(genBlock, ub, 1) + bc.BlockInfoDB.StoreBlockRecord(hash, br) + return bc +} + +// GenesisBlock creates the genesis Block, using the Config's +// InitialSubsidy and GenesisPublicKey. +func GenesisBlock(config *Config) *block.Block { + txo := &block.TransactionOutput{ + Amount: config.InitialSubsidy, + LockingScript: config.GenesisPublicKey, + } + genTx := &block.Transaction{ + Version: 0, + Inputs: nil, + Outputs: []*block.TransactionOutput{txo}, + LockTime: 0, + } + return &block.Block{ + Header: &block.Header{ + Version: 0, + PreviousHash: "", + MerkleRoot: "", + DifficultyTarget: "", + Nonce: 0, + Timestamp: 0, + }, + Transactions: []*block.Transaction{genTx}, + } +} + +// HandleBlock handles a new Block. At a high level, it: +// (1) Validates and stores the Block. +// (2) Stores the Block and resulting Undoblock to Disk. +// (3) Stores the BlockRecord in the BlockInfoDatabase. +// (4) Handles a fork, if necessary. +// (5) Updates the BlockChain's fields. +func (bc *BlockChain) HandleBlock(b *block.Block) { + //TODO +} + +// makeUndoBlock returns an UndoBlock given a slice of Transaction. +func (bc *BlockChain) makeUndoBlock(txs []*block.Transaction) *chainwriter.UndoBlock { + var transactionHashes []string + var outputIndexes []uint32 + var amounts []uint32 + var lockingScripts []string + for _, tx := range txs { + for _, txi := range tx.Inputs { + cl := coindatabase.CoinLocator{ + ReferenceTransactionHash: txi.ReferenceTransactionHash, + OutputIndex: txi.OutputIndex, + } + coin := bc.CoinDB.GetCoin(cl) + // if the coin is nil it means this isn't even a possible fork + if coin == nil { + return &chainwriter.UndoBlock{ + TransactionInputHashes: nil, + OutputIndexes: nil, + Amounts: nil, + LockingScripts: nil, + } + } + transactionHashes = append(transactionHashes, txi.ReferenceTransactionHash) + outputIndexes = append(outputIndexes, txi.OutputIndex) + amounts = append(amounts, coin.TransactionOutput.Amount) + lockingScripts = append(lockingScripts, coin.TransactionOutput.LockingScript) + } + } + return &chainwriter.UndoBlock{ + TransactionInputHashes: transactionHashes, + OutputIndexes: outputIndexes, + Amounts: amounts, + LockingScripts: lockingScripts, + } +} + +// getBlock uses the ChainWriter to retrieve a Block from Disk +// given that Block's hash +func (bc *BlockChain) getBlock(blockHash string) *block.Block { + br := bc.BlockInfoDB.GetBlockRecord(blockHash) + fi := &chainwriter.FileInfo{ + FileName: br.BlockFile, + StartOffset: br.BlockStartOffset, + EndOffset: br.BlockEndOffset, + } + return bc.ChainWriter.ReadBlock(fi) +} + +// getUndoBlock uses the ChainWriter to retrieve an UndoBlock +// from Disk given the corresponding Block's hash +func (bc *BlockChain) getUndoBlock(blockHash string) *chainwriter.UndoBlock { + br := bc.BlockInfoDB.GetBlockRecord(blockHash) + fi := &chainwriter.FileInfo{ + FileName: br.UndoFile, + StartOffset: br.UndoStartOffset, + EndOffset: br.UndoEndOffset, + } + return bc.ChainWriter.ReadUndoBlock(fi) +} + +// GetBlocks retrieves a slice of blocks from the main chain given a +// starting and ending height, inclusive. Given a chain of length 50, +// GetBlocks(10, 20) returns blocks 10 through 20. +func (bc *BlockChain) GetBlocks(start, end uint32) []*block.Block { + if start >= end || end <= 0 || start <= 0 || end > bc.Length { + utils.Debug.Printf("cannot get chain blocks with values start: %v end: %v", start, end) + } + + var blocks []*block.Block + currentHeight := bc.Length + nextHash := bc.LastBlock.Hash() + + for currentHeight >= start { + br := bc.BlockInfoDB.GetBlockRecord(nextHash) + fi := &chainwriter.FileInfo{ + FileName: br.BlockFile, + StartOffset: br.BlockStartOffset, + EndOffset: br.BlockEndOffset, + } + if currentHeight <= end { + nextBlock := bc.ChainWriter.ReadBlock(fi) + blocks = append(blocks, nextBlock) + } + nextHash = br.Header.PreviousHash + currentHeight-- + } + return reverseBlocks(blocks) +} + +// GetHashes retrieves a slice of hashes from the main chain given a +// starting and ending height, inclusive. Given a BlockChain of length +// 50, GetHashes(10, 20) returns the hashes of Blocks 10 through 20. +func (bc *BlockChain) GetHashes(start, end uint32) []string { + if start >= end || end <= 0 || start <= 0 || end > bc.Length { + utils.Debug.Printf("cannot get chain blocks with values start: %v end: %v", start, end) + } + + var hashes []string + currentHeight := bc.Length + nextHash := bc.LastBlock.Hash() + + for currentHeight >= start { + br := bc.BlockInfoDB.GetBlockRecord(nextHash) + if currentHeight <= end { + hashes = append(hashes, nextHash) + } + nextHash = br.Header.PreviousHash + currentHeight-- + } + return reverseHashes(hashes) +} + +// appendsToActiveChain returns whether a Block appends to the +// BlockChain's active chain or not. +func (bc *BlockChain) appendsToActiveChain(b *block.Block) bool { + return bc.LastBlock.Hash() == b.Header.PreviousHash +} + +// getForkedBlocks returns a slice of Blocks given a starting hash. +// It returns a maximum of maxHashes Blocks, where maxHashes is the +// BlockChain's maximum number of unsafe hashes. +func (bc *BlockChain) getForkedBlocks(startHash string) []*block.Block { + unsafeHashes := make(map[string]bool) + for _, h := range bc.UnsafeHashes { + unsafeHashes[h] = true + } + var forkedBlocks []*block.Block + nextHash := startHash + for i := 0; i < len(bc.UnsafeHashes); i++ { + forkedBlock := bc.getBlock(nextHash) + forkedBlocks = append(forkedBlocks, forkedBlock) + if _, ok := unsafeHashes[nextHash]; ok { + return forkedBlocks + } + nextHash = forkedBlock.Header.PreviousHash + } + return forkedBlocks +} + +// getBlocksAndUndoBlocks returns a slice of n Blocks with a +// corresponding slice of n UndoBlocks. +func (bc *BlockChain) getBlocksAndUndoBlocks(n int) ([]*block.Block, []*chainwriter.UndoBlock) { + var blocks []*block.Block + var undoBlocks []*chainwriter.UndoBlock + nextHash := bc.LastHash + for i := 0; i < n; i++ { + b := bc.getBlock(nextHash) + ub := bc.getUndoBlock(nextHash) + blocks = append(blocks, b) + undoBlocks = append(undoBlocks, ub) + nextHash = b.Header.PreviousHash + } + return blocks, undoBlocks +} + +// reverseBlocks returns a reversed slice of Blocks. +func reverseBlocks(s []*block.Block) []*block.Block { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } + return s +} + +// reverseHashes returns a reversed slice of hashes. +func reverseHashes(s []string) []string { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } + return s +} |