aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgithub-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com>2022-02-28 19:36:23 +0000
committergithub-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com>2022-02-28 19:36:23 +0000
commit1dd0508d5d3c737f1ee9c723f580baf73b1cfd70 (patch)
tree6adcc5ef85f9cf0bbb205c577da0bac9148114dd
Initial commit
-rw-r--r--README.md18
-rw-r--r--go.mod16
-rw-r--r--go.sum44
-rw-r--r--pkg/block/block.go90
-rw-r--r--pkg/block/transaction.go128
-rw-r--r--pkg/blockchain/blockchain.go261
-rw-r--r--pkg/blockchain/blockinfodatabase/blockinfodatabase.go32
-rw-r--r--pkg/blockchain/blockinfodatabase/blockrecord.go65
-rw-r--r--pkg/blockchain/blockinfodatabase/config.go12
-rw-r--r--pkg/blockchain/chainwriter/chainwriter.go127
-rw-r--r--pkg/blockchain/chainwriter/config.go23
-rw-r--r--pkg/blockchain/chainwriter/fileinfo.go8
-rw-r--r--pkg/blockchain/chainwriter/readwrite.go41
-rw-r--r--pkg/blockchain/chainwriter/undoblock.go60
-rw-r--r--pkg/blockchain/coindatabase/coin.go29
-rw-r--r--pkg/blockchain/coindatabase/coindatabase.go265
-rw-r--r--pkg/blockchain/coindatabase/coinrecord.go48
-rw-r--r--pkg/blockchain/coindatabase/config.go15
-rw-r--r--pkg/blockchain/config.go39
-rw-r--r--pkg/pro/chain.pb.go858
-rw-r--r--pkg/pro/chain.proto63
-rw-r--r--pkg/utils/logging.go55
-rw-r--r--pkg/utils/utils.go13
-rw-r--r--test/blockchain_test.go149
-rw-r--r--test/blockinfodatabase_test.go40
-rw-r--r--test/chainwriter_test.go129
-rw-r--r--test/coindatabase_test.go96
-rw-r--r--test/utils.go207
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.
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..3940ec2
--- /dev/null
+++ b/go.mod
@@ -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
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..72cea45
--- /dev/null
+++ b/go.sum
@@ -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,
+ }
+}