diff options
author | github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com> | 2022-02-28 19:36:23 +0000 |
---|---|---|
committer | github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com> | 2022-02-28 19:36:23 +0000 |
commit | 1dd0508d5d3c737f1ee9c723f580baf73b1cfd70 (patch) | |
tree | 6adcc5ef85f9cf0bbb205c577da0bac9148114dd |
Initial commit
28 files changed, 2931 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..57e95ef --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Chain + +## Introduction + +Chain is an example storage system (a blockchain) for a cryptocurrency. Chain is modeled after [Bitcoin's storage system](https://en.bitcoin.it/wiki/Bitcoin_Core_0.11_(ch_2):_Data_Storage), though heavily simplified. The goal of this project is to give you and your partner a taste of the many working components of a blockchain. + +### Components + +Chain consists of several main components. Here's a quick overview: + +1. **BlockChain** + - The main type of this project, **BlockChain** is a blockchain that stores and validates **Blocks**. It manages and updates its main chain based on the Blocks it receives. Watch out for forks! +2. **BlockInfoDatabase** + - The **BlockInfoDatabase** is a wrapper for a [LevelDB](https://en.wikipedia.org/wiki/LevelDB), storing information about each Block it receives in the form of a **BlockRecord**. In addition, each BlockRecord contains storage information for an **UndoBlock**, which provides additional information to revert a Block, should a fork occur. +3. **ChainWriter** + - The **ChainWriter** takes care of all I/O for the BlockChain. It writes and reads Blocks and UndoBlocks to Disk. +4. **CoinDatabase** + - **CoinDatabase** stores all UTXO information. It contains a cache of **Coins** and a LevelDB of **CoinRecords**. A Coin is a wrapper for a UTXO, and a CoinRecord is a record of all the UTXO created by a Transaction. Validation needs to be as quick as possible, which is why the CoinDatabase contains an in-memory cache in addition to a persistent database. Eventually the cache becomes too large, at which point the CoinDatabase must flush its cache to its database. @@ -0,0 +1,16 @@ +module Chain + +go 1.17 + +require ( + github.com/syndtr/goleveldb v1.0.0 + google.golang.org/protobuf v1.27.1 +) + +require ( + github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect + golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) @@ -0,0 +1,44 @@ +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/block/block.go b/pkg/block/block.go new file mode 100644 index 0000000..843f43f --- /dev/null +++ b/pkg/block/block.go @@ -0,0 +1,90 @@ +package block + +import ( + "Chain/pkg/pro" + "crypto/sha256" + "fmt" + "google.golang.org/protobuf/proto" +) + +// Header provides information about the Block. +// Version is the Block's version. +// PreviousHash is the hash of the previous Block. +// MerkleRoot is the hash of all the Block's Transactions. +// DifficultyTarget is the difficulty of achieving a winning Nonce. +// Nonce is a "number only used once" that satisfies the DifficultyTarget. +// Timestamp is when the Block was successfully mined. +type Header struct { + Version uint32 + PreviousHash string + MerkleRoot string + DifficultyTarget string + Nonce uint32 + Timestamp uint32 +} + +// Block includes a Header and a slice of Transactions. +type Block struct { + Header *Header + Transactions []*Transaction +} + +// EncodeHeader returns a pro.Header given a Header. +func EncodeHeader(header *Header) *pro.Header { + return &pro.Header{ + Version: header.Version, + PreviousHash: header.PreviousHash, + MerkleRoot: header.MerkleRoot, + DifficultyTarget: header.DifficultyTarget, + Nonce: header.Nonce, + Timestamp: header.Timestamp, + } +} + +// DecodeHeader returns a Header given a pro.Header. +func DecodeHeader(pheader *pro.Header) *Header { + return &Header{ + Version: pheader.GetVersion(), + PreviousHash: pheader.GetPreviousHash(), + MerkleRoot: pheader.GetMerkleRoot(), + DifficultyTarget: pheader.GetDifficultyTarget(), + Nonce: pheader.GetNonce(), + Timestamp: pheader.GetTimestamp(), + } +} + +// EncodeBlock returns a pro.Block given a Block. +func EncodeBlock(b *Block) *pro.Block { + var ptxs []*pro.Transaction + for _, tx := range b.Transactions { + ptxs = append(ptxs, EncodeTransaction(tx)) + } + return &pro.Block{ + Header: EncodeHeader(b.Header), + Transactions: ptxs, + } +} + +// DecodeBlock returns a Block given a pro.Block. +func DecodeBlock(pb *pro.Block) *Block { + var txs []*Transaction + for _, ptx := range pb.GetTransactions() { + txs = append(txs, DecodeTransaction(ptx)) + } + return &Block{ + Header: DecodeHeader(pb.GetHeader()), + Transactions: txs, + } +} + +// Hash returns the hash of the block (which is done via the header) +func (block *Block) Hash() string { + h := sha256.New() + pb := EncodeHeader(block.Header) + bytes, err := proto.Marshal(pb) + if err != nil { + fmt.Errorf("[block.Hash()] Unable to marshal block") + } + h.Write(bytes) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/pkg/block/transaction.go b/pkg/block/transaction.go new file mode 100644 index 0000000..0102120 --- /dev/null +++ b/pkg/block/transaction.go @@ -0,0 +1,128 @@ +package block + +import ( + "Chain/pkg/pro" + "crypto/sha256" + "fmt" + "google.golang.org/protobuf/proto" +) + +// TransactionInput is used as the input to create a TransactionOutput. +// Recall that TransactionInputs generate TransactionOutputs which in turn +// generate new TransactionInputs and so forth. +// ReferenceTransactionHash is the hash of the parent TransactionOutput's Transaction. +// OutputIndex is the index of the parent TransactionOutput's Transaction. +// Signature verifies that the payer can spend the referenced TransactionOutput. +type TransactionInput struct { + ReferenceTransactionHash string + OutputIndex uint32 + UnlockingScript string +} + +// TransactionOutput is an output created from a TransactionInput. +// Recall that TransactionOutputs generate TransactionInputs which in turn +// generate new TransactionOutputs and so forth. +// Amount is how much this TransactionOutput is worth. +// PublicKey is used to verify the payee's signature. +type TransactionOutput struct { + Amount uint32 + LockingScript string +} + +// Transaction contains information about a transaction. +// Version is the version of this transaction. +// Inputs is a slice of TransactionInputs. +// Outputs is a slice of TransactionOutputs. +// LockTime is the future time after which the Transaction is valid. +type Transaction struct { + Version uint32 + Inputs []*TransactionInput + Outputs []*TransactionOutput + LockTime uint32 +} + +// EncodeTransactionInput returns a pro.TransactionInput input +// given a TransactionInput. +func EncodeTransactionInput(txi *TransactionInput) *pro.TransactionInput { + return &pro.TransactionInput{ + ReferenceTransactionHash: txi.ReferenceTransactionHash, + OutputIndex: txi.OutputIndex, + UnlockingScript: txi.UnlockingScript, + } +} + +// DecodeTransactionInput returns a TransactionInput given +// a pro.TransactionInput. +func DecodeTransactionInput(ptxi *pro.TransactionInput) *TransactionInput { + return &TransactionInput{ + ReferenceTransactionHash: ptxi.GetReferenceTransactionHash(), + OutputIndex: ptxi.GetOutputIndex(), + UnlockingScript: ptxi.GetUnlockingScript(), + } +} + +// EncodeTransactionOutput returns a pro.TransactionOutput given +// a TransactionOutput. +func EncodeTransactionOutput(txo *TransactionOutput) *pro.TransactionOutput { + return &pro.TransactionOutput{ + Amount: txo.Amount, + LockingScript: txo.LockingScript, + } +} + +// DecodeTransactionOutput returns a TransactionOutput given +// a pro.TransactionOutput. +func DecodeTransactionOutput(ptxo *pro.TransactionOutput) *TransactionOutput { + return &TransactionOutput{ + Amount: ptxo.GetAmount(), + LockingScript: ptxo.GetLockingScript(), + } +} + +// EncodeTransaction returns a pro.Transaction given a Transaction. +func EncodeTransaction(tx *Transaction) *pro.Transaction { + var ptxis []*pro.TransactionInput + for _, txi := range tx.Inputs { + ptxis = append(ptxis, EncodeTransactionInput(txi)) + } + var ptxos []*pro.TransactionOutput + for _, txo := range tx.Outputs { + ptxos = append(ptxos, EncodeTransactionOutput(txo)) + } + return &pro.Transaction{ + Version: tx.Version, + Inputs: ptxis, + Outputs: ptxos, + LockTime: tx.LockTime, + } +} + +// DecodeTransaction returns a Transaction given a pro.Transaction. +func DecodeTransaction(ptx *pro.Transaction) *Transaction { + var txis []*TransactionInput + for _, ptxi := range ptx.GetInputs() { + txis = append(txis, DecodeTransactionInput(ptxi)) + } + var txos []*TransactionOutput + for _, ptxo := range ptx.GetOutputs() { + txos = append(txos, DecodeTransactionOutput(ptxo)) + } + return &Transaction{ + Version: ptx.GetVersion(), + Inputs: txis, + Outputs: txos, + LockTime: ptx.GetLockTime(), + } +} + +// Hash returns the hash of the transaction +func (tx *Transaction) Hash() string { + h := sha256.New() + pt := EncodeTransaction(tx) + bytes, err := proto.Marshal(pt) + if err != nil { + fmt.Errorf("[tx.Hash()] Unable to marshal transaction") + } + h.Write(bytes) + return fmt.Sprintf("%x", h.Sum(nil)) +} 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 +} diff --git a/pkg/blockchain/blockinfodatabase/blockinfodatabase.go b/pkg/blockchain/blockinfodatabase/blockinfodatabase.go new file mode 100644 index 0000000..c49a625 --- /dev/null +++ b/pkg/blockchain/blockinfodatabase/blockinfodatabase.go @@ -0,0 +1,32 @@ +package blockinfodatabase + +import ( + "Chain/pkg/utils" + "github.com/syndtr/goleveldb/leveldb" +) + +// BlockInfoDatabase is a wrapper for a levelDB +type BlockInfoDatabase struct { + db *leveldb.DB +} + +// New returns a BlockInfoDatabase given a Config +func New(config *Config) *BlockInfoDatabase { + db, err := leveldb.OpenFile(config.DatabasePath, nil) + if err != nil { + utils.Debug.Printf("Unable to initialize BlockInfoDatabase with path {%v}", config.DatabasePath) + } + return &BlockInfoDatabase{db: db} +} + +// StoreBlockRecord stores a BlockRecord in the BlockInfoDatabase. +func (blockInfoDB *BlockInfoDatabase) StoreBlockRecord(hash string, blockRecord *BlockRecord) { + //TODO +} + +// GetBlockRecord returns a BlockRecord from the BlockInfoDatabase given +// the relevant block's hash. +func (blockInfoDB *BlockInfoDatabase) GetBlockRecord(hash string) *BlockRecord { + //TODO + return nil +} diff --git a/pkg/blockchain/blockinfodatabase/blockrecord.go b/pkg/blockchain/blockinfodatabase/blockrecord.go new file mode 100644 index 0000000..8f8846a --- /dev/null +++ b/pkg/blockchain/blockinfodatabase/blockrecord.go @@ -0,0 +1,65 @@ +package blockinfodatabase + +import ( + "Chain/pkg/block" + "Chain/pkg/pro" +) + +// BlockRecord contains information about where a Block +// and its UndoBlock are stored on Disk. +// Header is the Block's Header. +// Height is the height of the Block. +// NumberOfTransactions is the number of Transactions in the Block. +// BlockFile is the name of the file where the Block is stored. +// BlockStartOffset is the starting offset of the Block within the +// BlockFile. +// BlockEndOffset is the ending offset of the Block within +// the BlockFile. +// UndoFile is the name of the file where the UndoBlock is stored. +// UndoStartOffset is the starting offset of the UndoBlock within +// the UndoFile. +// UndoEndOffset is the ending offset of the UndoBlock within the +// UndoFile. +type BlockRecord struct { + Header *block.Header + Height uint32 + NumberOfTransactions uint32 + + BlockFile string + BlockStartOffset uint32 + BlockEndOffset uint32 + + UndoFile string + UndoStartOffset uint32 + UndoEndOffset uint32 +} + +// EncodeBlockRecord returns a pro.BlockRecord given a BlockRecord. +func EncodeBlockRecord(br *BlockRecord) *pro.BlockRecord { + return &pro.BlockRecord{ + Header: block.EncodeHeader(br.Header), + Height: br.Height, + NumberOfTransactions: br.NumberOfTransactions, + BlockFile: br.BlockFile, + BlockStartOffset: br.BlockStartOffset, + BlockEndOffset: br.BlockEndOffset, + UndoFile: br.UndoFile, + UndoStartOffset: br.UndoStartOffset, + UndoEndOffset: br.UndoEndOffset, + } +} + +// DecodeBlockRecord returns a BlockRecord given a pro.BlockRecord. +func DecodeBlockRecord(pbr *pro.BlockRecord) *BlockRecord { + return &BlockRecord{ + Header: block.DecodeHeader(pbr.GetHeader()), + Height: pbr.GetHeight(), + NumberOfTransactions: pbr.GetNumberOfTransactions(), + BlockFile: pbr.GetBlockFile(), + BlockStartOffset: pbr.GetBlockStartOffset(), + BlockEndOffset: pbr.GetBlockEndOffset(), + UndoFile: pbr.GetUndoFile(), + UndoStartOffset: pbr.GetUndoStartOffset(), + UndoEndOffset: pbr.GetUndoEndOffset(), + } +} diff --git a/pkg/blockchain/blockinfodatabase/config.go b/pkg/blockchain/blockinfodatabase/config.go new file mode 100644 index 0000000..ec1990b --- /dev/null +++ b/pkg/blockchain/blockinfodatabase/config.go @@ -0,0 +1,12 @@ +package blockinfodatabase + +// Config is the BlockInfoDatabase's configuration options. +type Config struct { + DatabasePath string +} + +// DefaultConfig returns the default configuration for the +// BlockInfoDatabase. +func DefaultConfig() *Config { + return &Config{DatabasePath: "./blockinfodata"} +} 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, + } +} diff --git a/pkg/blockchain/coindatabase/coin.go b/pkg/blockchain/coindatabase/coin.go new file mode 100644 index 0000000..4281ac1 --- /dev/null +++ b/pkg/blockchain/coindatabase/coin.go @@ -0,0 +1,29 @@ +package coindatabase + +import "Chain/pkg/block" + +// Coin is used by the CoinDatabase to keep track of unspent +// TransactionOutputs. +// TransactionOutput is the underlying TransactionOutput. +// IsSpent is whether that TransactionOutput has been spent. +// Active is whether that TransactionOutput is one created by +// Blocks on the active Chain. +type Coin struct { + TransactionOutput *block.TransactionOutput + IsSpent bool +} + +// CoinLocator is a dumbed down TransactionInput, used +// as a key to Coins in the CoinDatabase's mainCache. +type CoinLocator struct { + ReferenceTransactionHash string + OutputIndex uint32 +} + +// makeCoinLocator returns a CoinLocator given a TransactionInput. +func makeCoinLocator(txi *block.TransactionInput) CoinLocator { + return CoinLocator{ + ReferenceTransactionHash: txi.ReferenceTransactionHash, + OutputIndex: txi.OutputIndex, + } +} diff --git a/pkg/blockchain/coindatabase/coindatabase.go b/pkg/blockchain/coindatabase/coindatabase.go new file mode 100644 index 0000000..83b3026 --- /dev/null +++ b/pkg/blockchain/coindatabase/coindatabase.go @@ -0,0 +1,265 @@ +package coindatabase + +import ( + "Chain/pkg/block" + "Chain/pkg/blockchain/chainwriter" + "Chain/pkg/pro" + "Chain/pkg/utils" + "fmt" + "github.com/syndtr/goleveldb/leveldb" + "google.golang.org/protobuf/proto" +) + +// CoinDatabase keeps track of Coins. +// db is a levelDB for persistent storage. +// mainCache stores as many Coins as possible for rapid validation. +// mainCacheSize is how many Coins are currently in the mainCache. +// mainCacheCapacity is the maximum number of Coins that the mainCache +// can store before it must flush. +type CoinDatabase struct { + db *leveldb.DB + mainCache map[CoinLocator]*Coin + mainCacheSize uint32 + mainCacheCapacity uint32 +} + +// New returns a CoinDatabase given a Config. +func New(config *Config) *CoinDatabase { + db, err := leveldb.OpenFile(config.DatabasePath, nil) + if err != nil { + utils.Debug.Printf("Unable to initialize BlockInfoDatabase with path {%v}", config.DatabasePath) + } + return &CoinDatabase{ + db: db, + mainCache: make(map[CoinLocator]*Coin), + mainCacheSize: 0, + mainCacheCapacity: config.MainCacheCapacity, + } +} + +// ValidateBlock returns whether a Block's Transactions are valid. +func (coinDB *CoinDatabase) ValidateBlock(transactions []*block.Transaction) bool { + for _, tx := range transactions { + if err := coinDB.validateTransaction(tx); err != nil { + utils.Debug.Printf("%v", err) + return false + } + } + return true +} + +// validateTransaction checks whether a Transaction's inputs are valid Coins. +// If the Coins have already been spent or do not exist, validateTransaction +// returns an error. +func (coinDB *CoinDatabase) validateTransaction(transaction *block.Transaction) error { + for _, txi := range transaction.Inputs { + key := makeCoinLocator(txi) + if coin, ok := coinDB.mainCache[key]; ok { + if coin.IsSpent { + return fmt.Errorf("[validateTransaction] coin already spent") + } + continue + } + if data, err := coinDB.db.Get([]byte(txi.ReferenceTransactionHash), nil); err != nil { + return fmt.Errorf("[validateTransaction] coin not in leveldb") + } else { + pcr := &pro.CoinRecord{} + if err2 := proto.Unmarshal(data, pcr); err2 != nil { + utils.Debug.Printf("Failed to unmarshal record from hash {%v}:", txi.ReferenceTransactionHash, err) + } + cr := DecodeCoinRecord(pcr) + if !contains(cr.OutputIndexes, txi.OutputIndex) { + return fmt.Errorf("[validateTransaction] coin record did not still contain output required for transaction input ") + } + } + } + return nil +} + +// UndoCoins handles reverting a Block. It: +// (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 +} + +// 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...) + return cr +} + +// FlushMainCache flushes the mainCache to the db. +func (coinDB *CoinDatabase) FlushMainCache() { + // update coin records + updatedCoinRecords := make(map[string]*CoinRecord) + for cl := range coinDB.mainCache { + // check whether we already updated this record + var cr *CoinRecord + + // (1) get our coin record + // first check our map, in case we already updated the coin record given + // a previous coin + if cr2, ok := updatedCoinRecords[cl.ReferenceTransactionHash]; ok { + cr = cr2 + } else { + // if we haven't already update this coin record, retrieve from db + data, err := coinDB.db.Get([]byte(cl.ReferenceTransactionHash), nil) + if err != nil { + utils.Debug.Printf("[FlushMainCache] coin record not in leveldb") + } + pcr := &pro.CoinRecord{} + if err = proto.Unmarshal(data, pcr); err != nil { + utils.Debug.Printf("Failed to unmarshal record from hash {%v}:%v", cl.ReferenceTransactionHash, err) + } + cr = DecodeCoinRecord(pcr) + } + // (2) remove the coin from the record if it's been spent + if coinDB.mainCache[cl].IsSpent { + cr = coinDB.removeCoinFromRecord(cr, cl.OutputIndex) + } + updatedCoinRecords[cl.ReferenceTransactionHash] = cr + delete(coinDB.mainCache, cl) + } + coinDB.mainCacheSize = 0 + // write the new records + for key, cr := range updatedCoinRecords { + if len(cr.OutputIndexes) <= 1 { + err := coinDB.db.Delete([]byte(key), nil) + if err != nil { + utils.Debug.Printf("[FlushMainCache] failed to delete key {%v}", key) + } + } else { + coinDB.putRecordInDB(key, cr) + } + } +} + +// StoreBlock handles storing a newly minted Block. It: +// (1) removes spent TransactionOutputs (if active) +// (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 +} + +// removeCoinFromDB removes a Coin from a CoinRecord, deleting the CoinRecord +// from the db entirely if it is the last remaining Coin in the CoinRecord. +func (coinDB *CoinDatabase) removeCoinFromDB(txHash string, cl CoinLocator) { + // 3. If the coin is not in the main cache, retrieve from the database + // 4. Delete coin records if they only have one coin + // 5. Remove coins from the record + cr := coinDB.getCoinRecordFromDB(txHash) + switch { + case cr == nil: + return + case len(cr.Amounts) <= 1: + if err := coinDB.db.Delete([]byte(txHash), nil); err != nil { + utils.Debug.Printf("[removeCoinFromDB] failed to remove {%v} from db", txHash) + } + default: + cr = coinDB.removeCoinFromRecord(cr, cl.OutputIndex) + coinDB.putRecordInDB(txHash, cr) + } +} + +// putRecordInDB puts a CoinRecord into the db. +func (coinDB *CoinDatabase) putRecordInDB(txHash string, cr *CoinRecord) { + record := EncodeCoinRecord(cr) + if err2 := coinDB.db.Put([]byte(txHash), []byte(record.String()), nil); err2 != nil { + utils.Debug.Printf("Unable to store block record for key {%v}", txHash) + } +} + +// removeCoinFromRecord returns an updated CoinRecord. It removes the Coin +// with the given outputIndex, if the Coin exists in the CoinRecord. +func (coinDB *CoinDatabase) removeCoinFromRecord(cr *CoinRecord, outputIndex uint32) *CoinRecord { + index := indexOf(cr.OutputIndexes, outputIndex) + if index < 0 { + return cr + } + cr.OutputIndexes = append(cr.OutputIndexes[:index], cr.OutputIndexes[index+1:]...) + cr.Amounts = append(cr.Amounts[:index], cr.Amounts[index+1:]...) + cr.LockingScripts = append(cr.LockingScripts[:index], cr.LockingScripts[index+1:]...) + return cr +} + +// createCoinRecord returns a CoinRecord for the provided Transaction. +func (coinDB *CoinDatabase) createCoinRecord(tx *block.Transaction) *CoinRecord { + var outputIndexes []uint32 + var amounts []uint32 + var LockingScripts []string + for i, txo := range tx.Outputs { + outputIndexes = append(outputIndexes, uint32(i)) + amounts = append(amounts, txo.Amount) + LockingScripts = append(LockingScripts, txo.LockingScript) + } + cr := &CoinRecord{ + Version: 0, + OutputIndexes: outputIndexes, + Amounts: amounts, + LockingScripts: LockingScripts, + } + return cr +} + +// getCoinRecordFromDB returns a CoinRecord from the db given a hash. +func (coinDB *CoinDatabase) getCoinRecordFromDB(txHash string) *CoinRecord { + if data, err := coinDB.db.Get([]byte(txHash), nil); err != nil { + utils.Debug.Printf("[validateTransaction] coin not in leveldb") + return nil + } else { + pcr := &pro.CoinRecord{} + if err := proto.Unmarshal(data, pcr); err != nil { + utils.Debug.Printf("Failed to unmarshal record from hash {%v}:", txHash, err) + } + cr := DecodeCoinRecord(pcr) + return cr + } +} + +// GetCoin returns a Coin given a CoinLocator. It first checks the +// mainCache, then checks the db. If the Coin doesn't exist, it returns nil. +func (coinDB *CoinDatabase) GetCoin(cl CoinLocator) *Coin { + if coin, ok := coinDB.mainCache[cl]; ok { + return coin + } + cr := coinDB.getCoinRecordFromDB(cl.ReferenceTransactionHash) + if cr == nil { + return nil + } + index := indexOf(cr.OutputIndexes, cl.OutputIndex) + if index < 0 { + return nil + } + return &Coin{ + TransactionOutput: &block.TransactionOutput{ + Amount: cr.Amounts[index], + LockingScript: cr.LockingScripts[index], + }, + IsSpent: false, + } +} + +// contains returns true if an int slice s contains element e, false if it does not. +func contains(s []uint32, e uint32) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +//indexOf returns the index of element e in int slice s, -1 if the element does not exist. +func indexOf(s []uint32, e uint32) int { + for i, a := range s { + if a == e { + return i + } + } + return -1 +} diff --git a/pkg/blockchain/coindatabase/coinrecord.go b/pkg/blockchain/coindatabase/coinrecord.go new file mode 100644 index 0000000..6de45c3 --- /dev/null +++ b/pkg/blockchain/coindatabase/coinrecord.go @@ -0,0 +1,48 @@ +package coindatabase + +import "Chain/pkg/pro" + +// CoinRecord is a record of which coins created by a Transaction +// have been spent. It is stored in the CoinDatabase's db. +type CoinRecord struct { + Version uint32 + OutputIndexes []uint32 + Amounts []uint32 + LockingScripts []string +} + +// EncodeCoinRecord returns a pro.CoinRecord given a CoinRecord. +func EncodeCoinRecord(cr *CoinRecord) *pro.CoinRecord { + var outputIndexes []uint32 + var amounts []uint32 + var lockingScripts []string + for i := 0; i < len(cr.OutputIndexes); i++ { + outputIndexes = append(outputIndexes, cr.OutputIndexes[i]) + amounts = append(amounts, cr.Amounts[i]) + lockingScripts = append(lockingScripts, cr.LockingScripts[i]) + } + return &pro.CoinRecord{ + Version: cr.Version, + OutputIndexes: outputIndexes, + Amounts: amounts, + LockingScripts: lockingScripts, + } +} + +// DecodeCoinRecord returns a CoinRecord given a pro.CoinRecord. +func DecodeCoinRecord(pcr *pro.CoinRecord) *CoinRecord { + var outputIndexes []uint32 + var amounts []uint32 + var lockingScripts []string + for i := 0; i < len(pcr.GetOutputIndexes()); i++ { + outputIndexes = append(outputIndexes, pcr.GetOutputIndexes()[i]) + amounts = append(amounts, pcr.GetAmounts()[i]) + lockingScripts = append(lockingScripts, pcr.GetLockingScripts()[i]) + } + return &CoinRecord{ + Version: pcr.GetVersion(), + OutputIndexes: outputIndexes, + Amounts: amounts, + LockingScripts: lockingScripts, + } +} diff --git a/pkg/blockchain/coindatabase/config.go b/pkg/blockchain/coindatabase/config.go new file mode 100644 index 0000000..bfb372b --- /dev/null +++ b/pkg/blockchain/coindatabase/config.go @@ -0,0 +1,15 @@ +package coindatabase + +// Config is the CoinDatabase's configuration options. +type Config struct { + DatabasePath string + MainCacheCapacity uint32 +} + +// DefaultConfig returns the CoinDatabase's default Config. +func DefaultConfig() *Config { + return &Config{ + DatabasePath: "./coindata", + MainCacheCapacity: 30, + } +} diff --git a/pkg/blockchain/config.go b/pkg/blockchain/config.go new file mode 100644 index 0000000..0ea3e7b --- /dev/null +++ b/pkg/blockchain/config.go @@ -0,0 +1,39 @@ +package blockchain + +import ( + "Chain/pkg/blockchain/blockinfodatabase" + "Chain/pkg/blockchain/chainwriter" + "Chain/pkg/blockchain/coindatabase" +) + +// Config is the BlockChain's configuration options. +type Config struct { + GenesisPublicKey string + InitialSubsidy uint32 + HasChn bool + BlockInfoDBPath string + ChainWriterDBPath string + CoinDBPath string +} + +// GENPK is the public key that was used +// for the genesis transaction on the +// genesis block. +var GENPK = "3059301306072a8648ce3d020106082a8648ce3d030107034200042418a20458559ae13a0d4bb6ac284c66a5cebb5689563d4cf573473d8c6d5abfa9a21a65dbb3ba2f2d930be7f763f940f9864abaf199a0f0d8d14bedda2dcad9" + +// GENPVK is the public key that was used +// for the genesis transaction on the +// genesis block. +var GENPVK = "307702010104202456b0e8bed5c27dcadb044df1af8eaf714084b61a23d17359fb09f3c3f5fff5a00a06082a8648ce3d030107a144034200042418a20458559ae13a0d4bb6ac284c66a5cebb5689563d4cf573473d8c6d5abfa9a21a65dbb3ba2f2d930be7f763f940f9864abaf199a0f0d8d14bedda2dcad9" + +// DefaultConfig returns the default configuration for the blockchain. +func DefaultConfig() *Config { + return &Config{ + GenesisPublicKey: GENPK, + InitialSubsidy: 0, + HasChn: true, + BlockInfoDBPath: blockinfodatabase.DefaultConfig().DatabasePath, + ChainWriterDBPath: chainwriter.DefaultConfig().DataDirectory, + CoinDBPath: coindatabase.DefaultConfig().DatabasePath, + } +} diff --git a/pkg/pro/chain.pb.go b/pkg/pro/chain.pb.go new file mode 100644 index 0000000..cefcb1e --- /dev/null +++ b/pkg/pro/chain.pb.go @@ -0,0 +1,858 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: chain.proto + +package pro + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Header struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + PreviousHash string `protobuf:"bytes,2,opt,name=previous_hash,json=previousHash,proto3" json:"previous_hash,omitempty"` + MerkleRoot string `protobuf:"bytes,3,opt,name=merkle_root,json=merkleRoot,proto3" json:"merkle_root,omitempty"` + DifficultyTarget string `protobuf:"bytes,4,opt,name=difficulty_target,json=difficultyTarget,proto3" json:"difficulty_target,omitempty"` + Nonce uint32 `protobuf:"varint,5,opt,name=nonce,proto3" json:"nonce,omitempty"` + Timestamp uint32 `protobuf:"varint,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (x *Header) Reset() { + *x = Header{} + if protoimpl.UnsafeEnabled { + mi := &file_chain_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Header) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Header) ProtoMessage() {} + +func (x *Header) ProtoReflect() protoreflect.Message { + mi := &file_chain_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Header.ProtoReflect.Descriptor instead. +func (*Header) Descriptor() ([]byte, []int) { + return file_chain_proto_rawDescGZIP(), []int{0} +} + +func (x *Header) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Header) GetPreviousHash() string { + if x != nil { + return x.PreviousHash + } + return "" +} + +func (x *Header) GetMerkleRoot() string { + if x != nil { + return x.MerkleRoot + } + return "" +} + +func (x *Header) GetDifficultyTarget() string { + if x != nil { + return x.DifficultyTarget + } + return "" +} + +func (x *Header) GetNonce() uint32 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *Header) GetTimestamp() uint32 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type TransactionInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ReferenceTransactionHash string `protobuf:"bytes,1,opt,name=reference_transaction_hash,json=referenceTransactionHash,proto3" json:"reference_transaction_hash,omitempty"` + OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` + UnlockingScript string `protobuf:"bytes,3,opt,name=unlocking_script,json=unlockingScript,proto3" json:"unlocking_script,omitempty"` +} + +func (x *TransactionInput) Reset() { + *x = TransactionInput{} + if protoimpl.UnsafeEnabled { + mi := &file_chain_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionInput) ProtoMessage() {} + +func (x *TransactionInput) ProtoReflect() protoreflect.Message { + mi := &file_chain_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionInput.ProtoReflect.Descriptor instead. +func (*TransactionInput) Descriptor() ([]byte, []int) { + return file_chain_proto_rawDescGZIP(), []int{1} +} + +func (x *TransactionInput) GetReferenceTransactionHash() string { + if x != nil { + return x.ReferenceTransactionHash + } + return "" +} + +func (x *TransactionInput) GetOutputIndex() uint32 { + if x != nil { + return x.OutputIndex + } + return 0 +} + +func (x *TransactionInput) GetUnlockingScript() string { + if x != nil { + return x.UnlockingScript + } + return "" +} + +type TransactionOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amount uint32 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` + LockingScript string `protobuf:"bytes,2,opt,name=locking_script,json=lockingScript,proto3" json:"locking_script,omitempty"` +} + +func (x *TransactionOutput) Reset() { + *x = TransactionOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_chain_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionOutput) ProtoMessage() {} + +func (x *TransactionOutput) ProtoReflect() protoreflect.Message { + mi := &file_chain_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionOutput.ProtoReflect.Descriptor instead. +func (*TransactionOutput) Descriptor() ([]byte, []int) { + return file_chain_proto_rawDescGZIP(), []int{2} +} + +func (x *TransactionOutput) GetAmount() uint32 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *TransactionOutput) GetLockingScript() string { + if x != nil { + return x.LockingScript + } + return "" +} + +type Transaction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + Inputs []*TransactionInput `protobuf:"bytes,2,rep,name=inputs,proto3" json:"inputs,omitempty"` + Outputs []*TransactionOutput `protobuf:"bytes,3,rep,name=outputs,proto3" json:"outputs,omitempty"` + LockTime uint32 `protobuf:"varint,4,opt,name=lock_time,json=lockTime,proto3" json:"lock_time,omitempty"` +} + +func (x *Transaction) Reset() { + *x = Transaction{} + if protoimpl.UnsafeEnabled { + mi := &file_chain_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Transaction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Transaction) ProtoMessage() {} + +func (x *Transaction) ProtoReflect() protoreflect.Message { + mi := &file_chain_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Transaction.ProtoReflect.Descriptor instead. +func (*Transaction) Descriptor() ([]byte, []int) { + return file_chain_proto_rawDescGZIP(), []int{3} +} + +func (x *Transaction) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Transaction) GetInputs() []*TransactionInput { + if x != nil { + return x.Inputs + } + return nil +} + +func (x *Transaction) GetOutputs() []*TransactionOutput { + if x != nil { + return x.Outputs + } + return nil +} + +func (x *Transaction) GetLockTime() uint32 { + if x != nil { + return x.LockTime + } + return 0 +} + +type Block struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header *Header `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + Transactions []*Transaction `protobuf:"bytes,2,rep,name=transactions,proto3" json:"transactions,omitempty"` +} + +func (x *Block) Reset() { + *x = Block{} + if protoimpl.UnsafeEnabled { + mi := &file_chain_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Block) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Block) ProtoMessage() {} + +func (x *Block) ProtoReflect() protoreflect.Message { + mi := &file_chain_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Block.ProtoReflect.Descriptor instead. +func (*Block) Descriptor() ([]byte, []int) { + return file_chain_proto_rawDescGZIP(), []int{4} +} + +func (x *Block) GetHeader() *Header { + if x != nil { + return x.Header + } + return nil +} + +func (x *Block) GetTransactions() []*Transaction { + if x != nil { + return x.Transactions + } + return nil +} + +type BlockRecord struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header *Header `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + Height uint32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + NumberOfTransactions uint32 `protobuf:"varint,3,opt,name=number_of_transactions,json=numberOfTransactions,proto3" json:"number_of_transactions,omitempty"` + BlockFile string `protobuf:"bytes,4,opt,name=block_file,json=blockFile,proto3" json:"block_file,omitempty"` + BlockStartOffset uint32 `protobuf:"varint,5,opt,name=block_start_offset,json=blockStartOffset,proto3" json:"block_start_offset,omitempty"` + BlockEndOffset uint32 `protobuf:"varint,6,opt,name=block_end_offset,json=blockEndOffset,proto3" json:"block_end_offset,omitempty"` + UndoFile string `protobuf:"bytes,7,opt,name=undo_file,json=undoFile,proto3" json:"undo_file,omitempty"` + UndoStartOffset uint32 `protobuf:"varint,8,opt,name=undo_start_offset,json=undoStartOffset,proto3" json:"undo_start_offset,omitempty"` + UndoEndOffset uint32 `protobuf:"varint,9,opt,name=undo_end_offset,json=undoEndOffset,proto3" json:"undo_end_offset,omitempty"` +} + +func (x *BlockRecord) Reset() { + *x = BlockRecord{} + if protoimpl.UnsafeEnabled { + mi := &file_chain_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockRecord) ProtoMessage() {} + +func (x *BlockRecord) ProtoReflect() protoreflect.Message { + mi := &file_chain_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockRecord.ProtoReflect.Descriptor instead. +func (*BlockRecord) Descriptor() ([]byte, []int) { + return file_chain_proto_rawDescGZIP(), []int{5} +} + +func (x *BlockRecord) GetHeader() *Header { + if x != nil { + return x.Header + } + return nil +} + +func (x *BlockRecord) GetHeight() uint32 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *BlockRecord) GetNumberOfTransactions() uint32 { + if x != nil { + return x.NumberOfTransactions + } + return 0 +} + +func (x *BlockRecord) GetBlockFile() string { + if x != nil { + return x.BlockFile + } + return "" +} + +func (x *BlockRecord) GetBlockStartOffset() uint32 { + if x != nil { + return x.BlockStartOffset + } + return 0 +} + +func (x *BlockRecord) GetBlockEndOffset() uint32 { + if x != nil { + return x.BlockEndOffset + } + return 0 +} + +func (x *BlockRecord) GetUndoFile() string { + if x != nil { + return x.UndoFile + } + return "" +} + +func (x *BlockRecord) GetUndoStartOffset() uint32 { + if x != nil { + return x.UndoStartOffset + } + return 0 +} + +func (x *BlockRecord) GetUndoEndOffset() uint32 { + if x != nil { + return x.UndoEndOffset + } + return 0 +} + +type CoinRecord struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + OutputIndexes []uint32 `protobuf:"varint,3,rep,packed,name=output_indexes,json=outputIndexes,proto3" json:"output_indexes,omitempty"` + Amounts []uint32 `protobuf:"varint,4,rep,packed,name=amounts,proto3" json:"amounts,omitempty"` + LockingScripts []string `protobuf:"bytes,5,rep,name=locking_scripts,json=lockingScripts,proto3" json:"locking_scripts,omitempty"` +} + +func (x *CoinRecord) Reset() { + *x = CoinRecord{} + if protoimpl.UnsafeEnabled { + mi := &file_chain_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CoinRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CoinRecord) ProtoMessage() {} + +func (x *CoinRecord) ProtoReflect() protoreflect.Message { + mi := &file_chain_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CoinRecord.ProtoReflect.Descriptor instead. +func (*CoinRecord) Descriptor() ([]byte, []int) { + return file_chain_proto_rawDescGZIP(), []int{6} +} + +func (x *CoinRecord) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *CoinRecord) GetOutputIndexes() []uint32 { + if x != nil { + return x.OutputIndexes + } + return nil +} + +func (x *CoinRecord) GetAmounts() []uint32 { + if x != nil { + return x.Amounts + } + return nil +} + +func (x *CoinRecord) GetLockingScripts() []string { + if x != nil { + return x.LockingScripts + } + return nil +} + +type UndoBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TransactionInputHashes []string `protobuf:"bytes,1,rep,name=transaction_input_hashes,json=transactionInputHashes,proto3" json:"transaction_input_hashes,omitempty"` + OutputIndexes []uint32 `protobuf:"varint,2,rep,packed,name=output_indexes,json=outputIndexes,proto3" json:"output_indexes,omitempty"` + Amounts []uint32 `protobuf:"varint,3,rep,packed,name=amounts,proto3" json:"amounts,omitempty"` + LockingScripts []string `protobuf:"bytes,4,rep,name=locking_scripts,json=lockingScripts,proto3" json:"locking_scripts,omitempty"` +} + +func (x *UndoBlock) Reset() { + *x = UndoBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_chain_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UndoBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UndoBlock) ProtoMessage() {} + +func (x *UndoBlock) ProtoReflect() protoreflect.Message { + mi := &file_chain_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UndoBlock.ProtoReflect.Descriptor instead. +func (*UndoBlock) Descriptor() ([]byte, []int) { + return file_chain_proto_rawDescGZIP(), []int{7} +} + +func (x *UndoBlock) GetTransactionInputHashes() []string { + if x != nil { + return x.TransactionInputHashes + } + return nil +} + +func (x *UndoBlock) GetOutputIndexes() []uint32 { + if x != nil { + return x.OutputIndexes + } + return nil +} + +func (x *UndoBlock) GetAmounts() []uint32 { + if x != nil { + return x.Amounts + } + return nil +} + +func (x *UndoBlock) GetLockingScripts() []string { + if x != nil { + return x.LockingScripts + } + return nil +} + +var File_chain_proto protoreflect.FileDescriptor + +var file_chain_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc9, 0x01, + 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x76, 0x69, + 0x6f, 0x75, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x72, 0x6b, 0x6c, + 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, + 0x72, 0x6b, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x66, 0x66, + 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x9e, 0x01, 0x0a, 0x10, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x3c, + 0x0a, 0x1a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x18, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, + 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, 0x6c, 0x6f, 0x63, + 0x6b, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x52, 0x0a, 0x11, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x69, + 0x6e, 0x67, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x9d, + 0x01, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x5a, + 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1f, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, + 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe4, 0x02, 0x0a, 0x0b, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x06, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x6f, 0x66, + 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, + 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x64, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x6e, 0x64, 0x6f, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x6e, 0x64, 0x6f, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x2a, 0x0a, + 0x11, 0x75, 0x6e, 0x64, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x75, 0x6e, 0x64, 0x6f, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x75, 0x6e, 0x64, + 0x6f, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0d, 0x75, 0x6e, 0x64, 0x6f, 0x45, 0x6e, 0x64, 0x4f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x22, 0x90, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0d, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0d, 0x52, 0x07, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x6c, + 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x09, 0x55, 0x6e, 0x64, 0x6f, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x12, 0x38, 0x0a, 0x18, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x27, 0x0a, + 0x0f, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x53, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2e, 0x2f, 0x70, 0x72, 0x6f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_chain_proto_rawDescOnce sync.Once + file_chain_proto_rawDescData = file_chain_proto_rawDesc +) + +func file_chain_proto_rawDescGZIP() []byte { + file_chain_proto_rawDescOnce.Do(func() { + file_chain_proto_rawDescData = protoimpl.X.CompressGZIP(file_chain_proto_rawDescData) + }) + return file_chain_proto_rawDescData +} + +var file_chain_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_chain_proto_goTypes = []interface{}{ + (*Header)(nil), // 0: Header + (*TransactionInput)(nil), // 1: TransactionInput + (*TransactionOutput)(nil), // 2: TransactionOutput + (*Transaction)(nil), // 3: Transaction + (*Block)(nil), // 4: Block + (*BlockRecord)(nil), // 5: BlockRecord + (*CoinRecord)(nil), // 6: CoinRecord + (*UndoBlock)(nil), // 7: UndoBlock +} +var file_chain_proto_depIdxs = []int32{ + 1, // 0: Transaction.inputs:type_name -> TransactionInput + 2, // 1: Transaction.outputs:type_name -> TransactionOutput + 0, // 2: Block.header:type_name -> Header + 3, // 3: Block.transactions:type_name -> Transaction + 0, // 4: BlockRecord.header:type_name -> Header + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_chain_proto_init() } +func file_chain_proto_init() { + if File_chain_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_chain_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Header); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chain_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionInput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chain_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionOutput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chain_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chain_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Block); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chain_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chain_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CoinRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chain_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UndoBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_chain_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_chain_proto_goTypes, + DependencyIndexes: file_chain_proto_depIdxs, + MessageInfos: file_chain_proto_msgTypes, + }.Build() + File_chain_proto = out.File + file_chain_proto_rawDesc = nil + file_chain_proto_goTypes = nil + file_chain_proto_depIdxs = nil +} diff --git a/pkg/pro/chain.proto b/pkg/pro/chain.proto new file mode 100644 index 0000000..0559684 --- /dev/null +++ b/pkg/pro/chain.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; + +option go_package = "../pro"; + +message Header { + uint32 version = 1; + string previous_hash = 2; + string merkle_root = 3; + string difficulty_target = 4; + uint32 nonce = 5; + uint32 timestamp = 6; +} + +message TransactionInput { + string reference_transaction_hash = 1; + uint32 output_index = 2; + string unlocking_script = 3; +} + +message TransactionOutput { + uint32 amount = 1; + string locking_script = 2; +} + +message Transaction { + uint32 version = 1; + repeated TransactionInput inputs = 2; + repeated TransactionOutput outputs = 3; + uint32 lock_time = 4; +} + +message Block { + Header header = 1; + repeated Transaction transactions = 2; +} + +message BlockRecord { + Header header = 1; + uint32 height = 2; + uint32 number_of_transactions = 3; + + string block_file = 4; + uint32 block_start_offset = 5; + uint32 block_end_offset = 6; + + string undo_file = 7; + uint32 undo_start_offset = 8; + uint32 undo_end_offset = 9; +} + +message CoinRecord { + uint32 version = 1; + repeated uint32 output_indexes = 3; + repeated uint32 amounts = 4; + repeated string locking_scripts = 5; +} + +message UndoBlock { + repeated string transaction_input_hashes = 1; + repeated uint32 output_indexes = 2; + repeated uint32 amounts = 3; + repeated string locking_scripts = 4; +}
\ No newline at end of file diff --git a/pkg/utils/logging.go b/pkg/utils/logging.go new file mode 100644 index 0000000..d98e78a --- /dev/null +++ b/pkg/utils/logging.go @@ -0,0 +1,55 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "strconv" + "strings" +) + +/* + * Brown University, CS1951L, Summer 2021 + * Designed by: Colby Anderson, John Roy + */ + +// Debug is optional logger for debugging +var Debug *log.Logger + +// Out is logger to Stdout +var Out *log.Logger + +// Err is logger to Stderr +var Err *log.Logger + +// init initializes the loggers. +func init() { + Debug = log.New(ioutil.Discard, "DEBUG: ", 0) + Out = log.New(os.Stdout, "INFO: ", log.Ltime|log.Lshortfile) + Err = log.New(os.Stderr, "ERROR: ", log.Ltime|log.Lshortfile) +} + +// SetDebug turns debug print statements on or off. +func SetDebug(enabled bool) { + if enabled { + Debug.SetOutput(os.Stdout) + } else { + Debug.SetOutput(ioutil.Discard) + } +} + +func FmtAddr(addr string) string { + if addr == "" { + return "" + } + colors := []string{"\033[41m", "\033[42m", "\033[43m", "\033[44m", "\033[45m", "\033[46m", "\033[47m"} + port, _ := strconv.ParseInt(strings.Split(addr, ":")[1], 10, 64) + randomColor := colors[int(port)%len(colors)] + return fmt.Sprintf("%v\033[97m[%v]\033[0m", randomColor, addr) +} + +func Colorize(s string, seed int) string { + lowestColor, highestColor := 104, 226 + return fmt.Sprintf("\033[38;5;%vm%v\033[0m", seed%(highestColor-lowestColor)+lowestColor, s) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..5055723 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,13 @@ +package utils + +import ( + "crypto/sha256" + "fmt" +) + +// Hash Adapted from: https://blog.8bitzen.com/posts/22-08-2019-how-to-hash-a-struct-in-go +func Hash(o interface{}) string { + h := sha256.New() + h.Write([]byte(fmt.Sprintf("%v", o))) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/test/blockchain_test.go b/test/blockchain_test.go new file mode 100644 index 0000000..412bb46 --- /dev/null +++ b/test/blockchain_test.go @@ -0,0 +1,149 @@ +package test + +import ( + "Chain/pkg/blockchain" + "Chain/pkg/utils" + "os" + "testing" +) + +func TestNewChain(t *testing.T) { + defer cleanUp() + bc := blockchain.New(blockchain.DefaultConfig()) + if bc.Length != 1 { + t.Errorf("Expected chain length: %v\n Actual chain length: %v", 1, bc.Length) + } + if _, err := os.Stat("./blockinfodata"); os.IsNotExist(err) { + t.Errorf("Did not create leveldb blockinfodata") + } + if _, err := os.Stat("./coindata"); os.IsNotExist(err) { + t.Errorf("Did not create leveldb coindata") + } + if len(bc.UnsafeHashes) != 0 { + t.Errorf("unsafe hashes not initialized properly") + } + if bc.LastBlock == nil { + t.Errorf("Did not initialize last block") + } +} + +func TestHandleAppendingBlock(t *testing.T) { + defer cleanUp() + bc := blockchain.New(blockchain.DefaultConfig()) + lastBlock := bc.LastBlock + newBlock := MakeBlockFromPrev(lastBlock) + bc.HandleBlock(newBlock) + if bc.Length != 2 { + t.Errorf("Expected chain length: %v\n Actual chain length: %v", 2, bc.Length) + } + if bc.LastHash != newBlock.Hash() { + t.Errorf("Expected last hash: %v\nActual last hash: %v", newBlock.Hash(), bc.LastHash) + } + if bc.LastBlock != newBlock { + t.Errorf("Expected block: %v\n Actual block: %v", newBlock, bc.LastBlock) + } +} + +func TestHandleForkingBlock(t *testing.T) { + defer cleanUp() + bc := blockchain.New(blockchain.DefaultConfig()) + currBlock := bc.LastBlock + currForkingBlock := bc.LastBlock + + for i := 0; i < 4; i++ { + newBlock := MakeBlockFromPrev(currBlock) + newForkingBlock := MakeBlockFromPrev(currForkingBlock) + newForkingBlock.Header.Version = 2 + if newBlock.Hash() == newForkingBlock.Hash() { + t.Errorf("Hashes should not be the same") + } + if i < 3 { + bc.HandleBlock(newBlock) + } + bc.HandleBlock(newForkingBlock) + currBlock = newBlock + currForkingBlock = newForkingBlock + } + if bc.Length != 5 { + t.Errorf("Expected chain length: %v\n Actual chain length: %v", 5, bc.Length) + } + if bc.LastHash != currForkingBlock.Hash() { + t.Errorf("Expected last hash: %v\nActual last hash: %v", currForkingBlock.Hash(), bc.LastHash) + } + if bc.LastBlock != currForkingBlock { + t.Errorf("Expected block: %v\n Actual block: %v", currForkingBlock, bc.LastBlock) + } +} + +func TestHandleInvalidBlock(t *testing.T) { + defer cleanUp() + bc := blockchain.New(blockchain.DefaultConfig()) + lastBlock := bc.LastBlock + block1 := MakeBlockFromPrev(lastBlock) + block2 := MakeBlockFromPrev(block1) + bc.HandleBlock(block2) + if bc.Length != 1 { + t.Errorf("Expected chain length: %v\n Actual chain length: %v", 1, bc.Length) + } + if bc.LastHash != lastBlock.Hash() { + t.Errorf("Expected last hash: %v\nActual last hash: %v", lastBlock.Hash(), bc.LastHash) + } + if bc.LastBlock != lastBlock { + t.Errorf("Expected block: %v\n Actual block: %v", lastBlock, bc.LastBlock) + } +} + +func TestHandle50Blocks(t *testing.T) { + defer cleanUp() + bc := blockchain.New(blockchain.DefaultConfig()) + currBlock := bc.LastBlock + for i := 0; i < 50; i++ { + newBlock := MakeBlockFromPrev(currBlock) + bc.HandleBlock(newBlock) + currBlock = bc.LastBlock + utils.Debug.Printf("iteration: %v/49", i) + } + if bc.Length != 51 { + t.Errorf("Expected chain length: %v\n Actual chain length: %v", 51, bc.Length) + } + if bc.LastHash != currBlock.Hash() { + t.Errorf("Expected last hash: %v\nActual last hash: %v", currBlock.Hash(), bc.LastHash) + } + if bc.LastBlock != currBlock { + t.Errorf("Expected block: %v\n Actual block: %v", currBlock, bc.LastBlock) + } +} + +func TestHandle2Forks(t *testing.T) { + defer cleanUp() + bc := blockchain.New(blockchain.DefaultConfig()) + currBlock := bc.LastBlock + currForkingBlock := bc.LastBlock + + for i := 0; i < 3; i++ { + newBlock := MakeBlockFromPrev(currBlock) + newForkingBlock := MakeBlockFromPrev(currForkingBlock) + newForkingBlock.Header.Version = 2 + if newBlock.Hash() == newForkingBlock.Hash() { + t.Errorf("Hashes should not be the same") + } + if i != 1 { + bc.HandleBlock(newBlock) + bc.HandleBlock(newForkingBlock) + } else { + bc.HandleBlock(newForkingBlock) + bc.HandleBlock(newBlock) + } + currBlock = newBlock + currForkingBlock = newForkingBlock + } + if bc.Length != 4 { + t.Errorf("Expected chain length: %v\n Actual chain length: %v", 4, bc.Length) + } + if bc.LastHash != currBlock.Hash() { + t.Errorf("Expected last hash: %v\nActual last hash: %v", currBlock.Hash(), bc.LastHash) + } + if bc.LastBlock != currBlock { + t.Errorf("Expected block: %v\n Actual block: %v", currBlock, bc.LastBlock) + } +} diff --git a/test/blockinfodatabase_test.go b/test/blockinfodatabase_test.go new file mode 100644 index 0000000..5205c99 --- /dev/null +++ b/test/blockinfodatabase_test.go @@ -0,0 +1,40 @@ +package test + +import ( + "Chain/pkg/blockchain/blockinfodatabase" + "reflect" + "testing" +) + +func TestStoreBlockRecord(t *testing.T) { + defer cleanUp() + blockinfo := blockinfodatabase.New(blockinfodatabase.DefaultConfig()) + br := MockedBlockRecord() + blockinfo.StoreBlockRecord("hash", br) +} + +func TestGetSameRecord(t *testing.T) { + defer cleanUp() + blockinfo := blockinfodatabase.New(blockinfodatabase.DefaultConfig()) + br := MockedBlockRecord() + blockinfo.StoreBlockRecord("hash", br) + br2 := blockinfo.GetBlockRecord("hash") + if !reflect.DeepEqual(br, br2) { + t.Errorf("Block records not equal") + } +} + +func TestGetDifferentRecords(t *testing.T) { + defer cleanUp() + blockinfo := blockinfodatabase.New(blockinfodatabase.DefaultConfig()) + br := MockedBlockRecord() + br2 := MockedBlockRecord() + br2.UndoEndOffset = 20 + blockinfo.StoreBlockRecord("hash", br) + blockinfo.StoreBlockRecord("hash2", br2) + rbr := blockinfo.GetBlockRecord("hash") + rbr2 := blockinfo.GetBlockRecord("hash2") + if reflect.DeepEqual(rbr, rbr2) { + t.Errorf("Block records should not be equal") + } +} diff --git a/test/chainwriter_test.go b/test/chainwriter_test.go new file mode 100644 index 0000000..4ea22ac --- /dev/null +++ b/test/chainwriter_test.go @@ -0,0 +1,129 @@ +package test + +import ( + "Chain/pkg/block" + "Chain/pkg/blockchain/chainwriter" + "google.golang.org/protobuf/proto" + "reflect" + "testing" +) + +func TestStoreOrphanBlock(t *testing.T) { + defer cleanUp() + cw := chainwriter.New(chainwriter.DefaultConfig()) + bl := MockedBlock() + ub := &chainwriter.UndoBlock{} + br := cw.StoreBlock(bl, ub, 0) + if br.BlockFile != "data/block_0.txt" { + t.Errorf("Expected file name: %v Actual file name: %v", "data/block_0.txt", br.BlockFile) + } + if br.UndoFile != "" { + t.Errorf("Expected file name: %v Actual file name: %v", "", br.UndoFile) + } +} + +func TestStoreBlock(t *testing.T) { + defer cleanUp() + cw := chainwriter.New(chainwriter.DefaultConfig()) + bl := MockedBlock() + ub := MockedUndoBlock() + br := cw.StoreBlock(bl, ub, 0) + if br.BlockFile != "data/block_0.txt" { + t.Errorf("Expected file name: %v Actual file name: %v", "data/block_0", br.BlockFile) + } + if br.UndoFile != "data/undo_0.txt" { + t.Errorf("Expected file name: %v Actual file name: %v", "", br.UndoFile) + } +} + +func TestWriteBlock(t *testing.T) { + defer cleanUp() + cw := chainwriter.New(chainwriter.DefaultConfig()) + b := MockedBlock() + pb := block.EncodeBlock(b) + serializedBlock, _ := proto.Marshal(pb) + fi := cw.WriteBlock(serializedBlock) + if fi.StartOffset != 0 { + t.Errorf("Expected start offset: %v\nActual start offset: %v", 0, fi.StartOffset) + } + if int(fi.EndOffset) != len(serializedBlock) { + t.Errorf("Expected end offset: %v\nActual end offset: %v", 0, fi.EndOffset) + } + if fi.FileName != "data/block_0.txt" { + t.Errorf("Expected file name: %v Actual file name: %v", "data/block_0", fi.FileName) + } +} + +func TestWriteUndoBlock(t *testing.T) { + defer cleanUp() + cw := chainwriter.New(chainwriter.DefaultConfig()) + ub := MockedUndoBlock() + pub := chainwriter.EncodeUndoBlock(ub) + serializedUndoBlock, _ := proto.Marshal(pub) + ufi := cw.WriteUndoBlock(serializedUndoBlock) + if ufi.StartOffset != 0 { + t.Errorf("Expected start offset: %v\nActual start offset: %v", 0, ufi.StartOffset) + } + if int(ufi.EndOffset) != len(serializedUndoBlock) { + t.Errorf("Expected end offset: %v\nActual end offset: %v", 0, ufi.EndOffset) + } + if ufi.FileName != "data/undo_0.txt" { + t.Errorf("Expected file name: %v Actual file name: %v", "data/block_0", ufi.FileName) + } +} + +func TestReadBlock(t *testing.T) { + defer cleanUp() + cw := chainwriter.New(chainwriter.DefaultConfig()) + b := MockedBlock() + pb := block.EncodeBlock(b) + serializedBlock, _ := proto.Marshal(pb) + fi := cw.WriteBlock(serializedBlock) + b2 := cw.ReadBlock(fi) + if !reflect.DeepEqual(b, b2) { + t.Errorf("Expected block: %v\nActual block: %v", b, b2) + } +} + +func TestReadUndoBlock(t *testing.T) { + defer cleanUp() + cw := chainwriter.New(chainwriter.DefaultConfig()) + ub := MockedUndoBlock() + pub := chainwriter.EncodeUndoBlock(ub) + serializedUndoBlock, _ := proto.Marshal(pub) + ufi := cw.WriteUndoBlock(serializedUndoBlock) + ub2 := cw.ReadUndoBlock(ufi) + if !reflect.DeepEqual(ub, ub2) { + t.Errorf("Expected block: %v\nActual block: %v", ub, ub2) + } +} + +func TestRead100Blocks(t *testing.T) { + defer cleanUp() + config := chainwriter.DefaultConfig() + config.MaxBlockFileSize = 100 + cw := chainwriter.New(config) + + var blocks []*block.Block + var fileInfos []*chainwriter.FileInfo + + // write blocks + for i := 0; i < 100; i++ { + b := MockedBlock() + b.Header.Nonce = uint32(i) + blocks = append(blocks, b) + pb := block.EncodeBlock(b) + serializedBlock, _ := proto.Marshal(pb) + fi := cw.WriteBlock(serializedBlock) + fileInfos = append(fileInfos, fi) + } + + // read blocks + for i := 0; i < 100; i++ { + b := cw.ReadBlock(fileInfos[i]) + if !reflect.DeepEqual(blocks[i], b) { + t.Errorf("Block: %v/99\nExpected block: %v\nActual block: %v", i, blocks[i], b) + } + } + +} diff --git a/test/coindatabase_test.go b/test/coindatabase_test.go new file mode 100644 index 0000000..8391fba --- /dev/null +++ b/test/coindatabase_test.go @@ -0,0 +1,96 @@ +package test + +import ( + "Chain/pkg/block" + "Chain/pkg/blockchain/chainwriter" + "Chain/pkg/blockchain/coindatabase" + "reflect" + "testing" +) + +func TestValidateValidBlock(t *testing.T) { + defer cleanUp() + genBlock := GenesisBlock() + coinDB := coindatabase.New(coindatabase.DefaultConfig()) + coinDB.StoreBlock(genBlock.Transactions, true) + block1 := MakeBlockFromPrev(genBlock) + if !coinDB.ValidateBlock(block1.Transactions) { + t.Errorf("block1 should have validated") + } +} + +func TestValidateInvalidBlock(t *testing.T) { + defer cleanUp() + genBlock := GenesisBlock() + coinDB := coindatabase.New(coindatabase.DefaultConfig()) + coinDB.StoreBlock(genBlock.Transactions, true) + block1 := MakeBlockFromPrev(genBlock) + block2 := MakeBlockFromPrev(block1) + if coinDB.ValidateBlock(block2.Transactions) { + t.Errorf("block2 should not have validated") + } +} + +func TestUndoCoins(t *testing.T) { + defer cleanUp() + genBlock := GenesisBlock() + coinDB := coindatabase.New(coindatabase.DefaultConfig()) + coinDB.StoreBlock(genBlock.Transactions, true) + block1 := MakeBlockFromPrev(genBlock) + coinDB.StoreBlock(block1.Transactions, true) + block2 := MakeBlockFromPrev(block1) + ub2 := UndoBlockFromBlock(block2) + coinDB.StoreBlock(block2.Transactions, true) + coinDB.UndoCoins([]*block.Block{block2}, []*chainwriter.UndoBlock{ub2}) + // make sure coins from undo block are put back + for i := 0; i < len(ub2.TransactionInputHashes); i++ { + cl := coindatabase.CoinLocator{ + ReferenceTransactionHash: ub2.TransactionInputHashes[i], + OutputIndex: ub2.OutputIndexes[i], + } + coin := coinDB.GetCoin(cl) + if coin == nil { + t.Errorf("coin should exist") + } else { + if coin.IsSpent { + t.Errorf("coin should not be spent") + } + } + } + // make sure coins from block are deleted + for _, tx := range block2.Transactions { + txHash := tx.Hash() + for i := 0; i < len(tx.Outputs); i++ { + cl := coindatabase.CoinLocator{ + ReferenceTransactionHash: txHash, + OutputIndex: uint32(i), + } + if coin := coinDB.GetCoin(cl); coin != nil { + t.Errorf("Coin should not exist") + } + } + } +} + +func TestGetCoin(t *testing.T) { + defer cleanUp() + genBlock := GenesisBlock() + coinDB := coindatabase.New(coindatabase.DefaultConfig()) + coinDB.StoreBlock(genBlock.Transactions, true) + txHash := genBlock.Transactions[0].Hash() + cl := coindatabase.CoinLocator{ + ReferenceTransactionHash: txHash, + OutputIndex: 0, + } + coin := coinDB.GetCoin(cl) + if coin.IsSpent { + t.Errorf("Expected coin.IsSpent: %v\nActual coin.IsSpent:%v", false, coin.IsSpent) + } + if !reflect.DeepEqual(coin.TransactionOutput, genBlock.Transactions[0].Outputs[0]) { + t.Errorf( + "Expected transaction output: %v\nActual transactionoutput:%v", + genBlock.Transactions[0].Outputs[0], + coin.TransactionOutput, + ) + } +} diff --git a/test/utils.go b/test/utils.go new file mode 100644 index 0000000..cd0c63e --- /dev/null +++ b/test/utils.go @@ -0,0 +1,207 @@ +package test + +import ( + "Chain/pkg/block" + "Chain/pkg/blockchain/blockinfodatabase" + "Chain/pkg/blockchain/chainwriter" + "fmt" + "os" +) + +// cleanUp removes any directories created by a test. +func cleanUp() { + removeBlockInfoDB() + removeCoinDB() + removeDataDB() +} + +// removeCoinDB removes the coin database's level db. +func removeCoinDB() { + if _, err := os.Stat("./coindata"); !os.IsNotExist(err) { + if err2 := os.RemoveAll("./coindata"); err2 != nil { + fmt.Errorf("coudld not remove leveldb coindata") + } + } +} + +// removeBlockInfoDB removes the block info database's level db. +func removeBlockInfoDB() { + if _, err := os.Stat("./blockinfodata"); !os.IsNotExist(err) { + if err2 := os.RemoveAll("./blockinfodata"); err2 != nil { + fmt.Errorf("coudld not remove leveldb blockinfodata") + } + } +} + +//removeDataDB removes the chain writer's data directory. +func removeDataDB() { + if _, err := os.Stat("./data"); !os.IsNotExist(err) { + if err2 := os.RemoveAll("./data"); err2 != nil { + fmt.Errorf("coudld not remove directory data") + } + } +} + +// MockedHeader returns a mocked Header. +func MockedHeader() *block.Header { + return &block.Header{ + Version: 0, + PreviousHash: "", + MerkleRoot: "", + DifficultyTarget: "", + Nonce: 0, + Timestamp: 0, + } +} + +// MockedBlockRecord returns a mocked BlockRecord. +func MockedBlockRecord() *blockinfodatabase.BlockRecord { + return &blockinfodatabase.BlockRecord{ + Header: MockedHeader(), + Height: 0, + NumberOfTransactions: 0, + BlockFile: "./blockinfodata/block_0", + BlockStartOffset: 0, + BlockEndOffset: 10, + UndoFile: "", + UndoStartOffset: 0, + UndoEndOffset: 0, + } +} + +// MockedTransactionInput returns a mocked TransactionInput. +func MockedTransactionInput() *block.TransactionInput { + return &block.TransactionInput{ + ReferenceTransactionHash: "", + OutputIndex: 0, + UnlockingScript: "", + } +} + +// MockedTransactionOutput returns a mocked TransactionOutput. +func MockedTransactionOutput() *block.TransactionOutput { + return &block.TransactionOutput{ + Amount: 0, + LockingScript: "", + } +} + +// MockedBlock returns a mocked Transaction. +func MockedTransaction() *block.Transaction { + return &block.Transaction{ + Version: 0, + Inputs: []*block.TransactionInput{MockedTransactionInput()}, + Outputs: []*block.TransactionOutput{MockedTransactionOutput()}, + LockTime: 0, + } +} + +// MockedBlock returns a mocked Block. +func MockedBlock() *block.Block { + return &block.Block{ + Header: MockedHeader(), + Transactions: []*block.Transaction{MockedTransaction()}, + } +} + +// MockedUndoBlock returns a mocked UndoBlock. +func MockedUndoBlock() *chainwriter.UndoBlock { + return &chainwriter.UndoBlock{ + TransactionInputHashes: []string{""}, + OutputIndexes: []uint32{1}, + Amounts: []uint32{1}, + LockingScripts: []string{""}, + } +} + +//GenesisBlock returns the genesis block for testing purposes. +func GenesisBlock() *block.Block { + txo := &block.TransactionOutput{ + Amount: 1_000_000_000, + LockingScript: "pubkey", + } + 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}, + } +} + +// MakeBlockFromPrev creates a new Block from an existing Block, +// using the old Block's TransactionOutputs as TransactionInputs +// for the new Transaction. +func MakeBlockFromPrev(b *block.Block) *block.Block { + newHeader := &block.Header{ + Version: 0, + PreviousHash: b.Hash(), + MerkleRoot: "", + DifficultyTarget: "", + Nonce: 0, + Timestamp: 0, + } + + var transactions []*block.Transaction + + for _, tx := range b.Transactions { + txHash := tx.Hash() + for i, txo := range tx.Outputs { + txi := &block.TransactionInput{ + ReferenceTransactionHash: txHash, + OutputIndex: uint32(i), + UnlockingScript: "", + } + txo1 := &block.TransactionOutput{ + Amount: txo.Amount / 2, + LockingScript: "", + } + tx1 := &block.Transaction{ + Version: uint32(i), + Inputs: []*block.TransactionInput{txi}, + Outputs: []*block.TransactionOutput{txo1}, + LockTime: 0, + } + transactions = append(transactions, tx1) + } + } + return &block.Block{ + Header: newHeader, + Transactions: transactions, + } +} + +// UndoBlockFromBlock creates an UndoBlock from a Block. +// This function only works because we're not using inputs from +// other Blocks. It also does not actually take care of amounts +// or public keys. +func UndoBlockFromBlock(b *block.Block) *chainwriter.UndoBlock { + var transactionHashes []string + var outputIndexes []uint32 + var amounts []uint32 + var lockingScripts []string + for _, tx := range b.Transactions { + for _, txi := range tx.Inputs { + transactionHashes = append(transactionHashes, txi.ReferenceTransactionHash) + outputIndexes = append(outputIndexes, txi.OutputIndex) + amounts = append(amounts, 0) + lockingScripts = append(lockingScripts, "") + } + } + return &chainwriter.UndoBlock{ + TransactionInputHashes: transactionHashes, + OutputIndexes: outputIndexes, + Amounts: amounts, + LockingScripts: lockingScripts, + } +} |