From c742b03636668db461c77b02ac6a5135417f5818 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Mon, 27 Jun 2022 19:47:13 +0100 Subject: [PATCH 01/13] Add go boilerplate --- .github/workflows/build_go.yml | 4 ++++ go.mod | 3 +++ main.go | 7 +++++++ 3 files changed, 14 insertions(+) create mode 100644 go.mod create mode 100644 main.go diff --git a/.github/workflows/build_go.yml b/.github/workflows/build_go.yml index 4d990cf..0e0c83f 100644 --- a/.github/workflows/build_go.yml +++ b/.github/workflows/build_go.yml @@ -23,3 +23,7 @@ jobs: - name: Test run: go test -v ./... + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v3.2.0 + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ebf7611 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module mvinvchain + +go 1.18 diff --git a/main.go b/main.go new file mode 100644 index 0000000..91cca4f --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, world.") +} From ed062e74551690e7daf72f470ce5edac7d35fac0 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Mon, 27 Jun 2022 22:44:17 +0100 Subject: [PATCH 02/13] Basic block creation and mining --- block.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ block_test.go | 25 +++++++++++++++++++++ blockchain.go | 6 +++++ main.go | 38 ++++++++++++++++++++++++++++++-- output.go | 6 +++++ transaction.go | 6 +++++ 6 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 block.go create mode 100644 block_test.go create mode 100644 blockchain.go create mode 100644 output.go create mode 100644 transaction.go diff --git a/block.go b/block.go new file mode 100644 index 0000000..376c457 --- /dev/null +++ b/block.go @@ -0,0 +1,59 @@ +package main + +import ( + "crypto/sha256" + "encoding/json" + "strconv" + "strings" +) + +type Block struct { + index int + unixTimeStamp int64 + hash []byte + previousHash []byte + nonce int + difficulty int + transaction []Transaction +} + +func newBlock(index int, unixTimeStamp int64, previousHash []byte, difficulty int, transaction []Transaction) *Block { + b := &Block{ + index: index, + unixTimeStamp: unixTimeStamp, + hash: make([]byte, 32), + previousHash: previousHash, + nonce: 0, + difficulty: difficulty, + transaction: transaction, + } + + b.mine() + + return b +} + +func (b *Block) mine() { + for !b.validateHash(b.calculateHash()) { + b.nonce++ + b.hash = b.calculateHash() + } +} + +func (b *Block) calculateHash() []byte { + hash := sha256.Sum256([]byte(strconv.Itoa(b.index) + strconv.FormatInt(b.unixTimeStamp, 10) + string(b.previousHash) + strconv.Itoa(b.nonce) + strconv.Itoa(b.difficulty) + transactionToString(b.transaction))) + return hash[:] +} + +func (b *Block) validateHash(hash []byte) bool { + prefix := strings.Repeat("0", b.difficulty) + return strings.HasPrefix(string(hash), prefix) +} + +func first(n []byte, _ error) []byte { + return n +} + +func transactionToString(transaction []Transaction) string { + return string(first(json.Marshal(transaction))) +} diff --git a/block_test.go b/block_test.go new file mode 100644 index 0000000..bfe95d5 --- /dev/null +++ b/block_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "testing" +) + +func TestTest(t *testing.T) { + got := test() + want := "Hello, world." + + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func BenchmarkTest(b *testing.B) { + for i := 0; i < b.N; i++ { + test() + } +} + +func ExampleMain() { + main() + // Output: Hello, world. +} diff --git a/blockchain.go b/blockchain.go new file mode 100644 index 0000000..d1dce92 --- /dev/null +++ b/blockchain.go @@ -0,0 +1,6 @@ +package main + +type Blockchain struct { + genesisBlock Block + blocks []Block +} diff --git a/main.go b/main.go index 91cca4f..27c742d 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,41 @@ package main -import "fmt" +import ( + "fmt" + "time" +) func main() { - fmt.Println("Hello, world.") + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + blockchain := Blockchain{ + genesisBlock: genesisBlock, + blocks: []Block{genesisBlock}, + } + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + fmt.Println(block) + + fmt.Println(blockchain) +} + +func test() string { + return "Hello, world." } diff --git a/output.go b/output.go new file mode 100644 index 0000000..eb393ae --- /dev/null +++ b/output.go @@ -0,0 +1,6 @@ +package main + +type Output struct { + address string + value int +} diff --git a/transaction.go b/transaction.go new file mode 100644 index 0000000..a9afad9 --- /dev/null +++ b/transaction.go @@ -0,0 +1,6 @@ +package main + +type Transaction struct { + inputs []Output + outputs []Output +} From d9a5820fbfba6881d8f97c2bde8f1d930b038ad5 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Mon, 27 Jun 2022 22:45:37 +0100 Subject: [PATCH 03/13] Fix tests --- block_test.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/block_test.go b/block_test.go index bfe95d5..5b2e6d0 100644 --- a/block_test.go +++ b/block_test.go @@ -1,25 +1,21 @@ package main -import ( - "testing" -) +// func TestTest(t *testing.T) { +// got := test() +// want := "Hello, world." -func TestTest(t *testing.T) { - got := test() - want := "Hello, world." +// if got != want { +// t.Errorf("got %q, want %q", got, want) +// } +// } - if got != want { - t.Errorf("got %q, want %q", got, want) - } -} +// func BenchmarkTest(b *testing.B) { +// for i := 0; i < b.N; i++ { +// test() +// } +// } -func BenchmarkTest(b *testing.B) { - for i := 0; i < b.N; i++ { - test() - } -} - -func ExampleMain() { - main() - // Output: Hello, world. -} +// func ExampleMain() { +// main() +// // Output: Hello, world. +// } From ed1d6c7c0de1ba355dc56eb3a5b405f49f5e2d4b Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Tue, 28 Jun 2022 21:54:31 +0100 Subject: [PATCH 04/13] Adding blocks to the blockchain, validation, errors and error tests --- block_errors_test.go | 592 +++++++++++++++++++++++++++++++++++++++++++ block_test.go | 21 -- blockchain.go | 89 +++++++ errors.go | 160 ++++++++++++ main.go | 34 ++- output.go | 1 + transaction.go | 20 ++ 7 files changed, 886 insertions(+), 31 deletions(-) create mode 100644 block_errors_test.go delete mode 100644 block_test.go create mode 100644 errors.go diff --git a/block_errors_test.go b/block_errors_test.go new file mode 100644 index 0000000..63f52b1 --- /dev/null +++ b/block_errors_test.go @@ -0,0 +1,592 @@ +package main + +import ( + "testing" + "time" +) + +// func BenchmarkTest(b *testing.B) { +// for i := 0; i < b.N; i++ { +// test() +// } +// } + +// func ExampleMain() { +// main() +// // Output: Hello, world. +// } + +// MismatchedIndex Tests +func TestNewBlockMismatchedIndexLess(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + -1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockMismatchedIndexGreater(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 2, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockMismatchedIndexEqual(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 0, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockMismatchedIndex(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// AchronologicalTimeStamp Test +func TestNewBlockAchronologicalTimeStamp(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + 10, + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockChronologicalTimeStamp(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// InvalidTimestamp Test +func TestNewBlockInvalidTimestamp(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano()+1000000000000000000, + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockValidTimestamp(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// InvalidPreviousHash Test +func TestNewBlockInvalidPreviousHashGenesis(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockInvalidPreviousHash(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + bc.addBlock(*block) + + block2 := newBlock( + 2, + time.Now().UTC().UnixNano(), + []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + 1, + []Transaction{}, + ) + + block2.mine() + + if bc.addBlock(*block2) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestValidPreviousHashGenesis(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +func TestValidPreviousHash(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + bc.addBlock(*block) + + block2 := newBlock( + 2, + time.Now().UTC().UnixNano(), + block.hash, + 1, + []Transaction{}, + ) + + block2.mine() + + if bc.addBlock(*block2) != nil { + t.Errorf("Expected false, got true") + } +} + +// InvalidGenesisBlock Test +func TestNewBlockInvalidGenesisBlock(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: []byte{1, 2, 3, 4, 5, 6}, + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockValidGenesisBlock(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{}, + ) + + block.mine() + + if bc.addBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// InsufficientInputValue Test +func TestNewBlockInsufficientInputValue(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{ + { + inputs: []Output{ + { + index: 0, + address: "Bob", + value: 1, + }, + }, + outputs: []Output{ + { + index: 0, + address: "Alice", + value: 2, + }, + }, + }, + }, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockSufficientInputValue(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{ + { + inputs: []Output{ + { + index: 0, + address: "Bob", + value: 1, + }, + }, + outputs: []Output{ + { + index: 0, + address: "Alice", + value: 1, + }, + }, + }, + }, + ) + + block.mine() + + if bc.addBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// InvalidInput Test +func TestNewBlockInvalidInput(t *testing.T) { + genesisBlock := Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: nil, + } + + bc := Blockchain{} + bc.newBlockchain(genesisBlock) + + block := newBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.hash, + 1, + []Transaction{ + { + inputs: []Output{ + { + index: 0, + address: "Bob", + value: -1, + }, + }, + outputs: []Output{ + { + index: 0, + address: "Alice", + value: -1, + }, + }, + }, + }, + ) + + block.mine() + + if bc.addBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} diff --git a/block_test.go b/block_test.go deleted file mode 100644 index 5b2e6d0..0000000 --- a/block_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -// func TestTest(t *testing.T) { -// got := test() -// want := "Hello, world." - -// if got != want { -// t.Errorf("got %q, want %q", got, want) -// } -// } - -// func BenchmarkTest(b *testing.B) { -// for i := 0; i < b.N; i++ { -// test() -// } -// } - -// func ExampleMain() { -// main() -// // Output: Hello, world. -// } diff --git a/blockchain.go b/blockchain.go index d1dce92..fc8a060 100644 --- a/blockchain.go +++ b/blockchain.go @@ -1,6 +1,95 @@ package main +import ( + "bytes" + "fmt" + "time" +) + type Blockchain struct { genesisBlock Block blocks []Block } + +func (bc *Blockchain) newBlockchain(genesisBlock Block) { + bc.genesisBlock = genesisBlock + bc.blocks = []Block{genesisBlock} +} + +func (bc *Blockchain) addBlock(block Block) error { + if err := bc.isValidBlock(block); err != nil { + fmt.Printf(err.Error()) + return err + } + + bc.blocks = append(bc.blocks, block) + + return nil +} + +func (bc *Blockchain) isValidBlock(block Block) error { + if bc.isInvalidIndex(block) { + return MismatchedIndex{expectedIndex: len(bc.blocks), actualIndex: block.index}.doError() + } + if bc.isAchronTimestamp(block) { + return AchronologicalTimestamp{expectedTimestamp: bc.blocks[len(bc.blocks)-1].unixTimeStamp, actualTimestamp: block.unixTimeStamp}.doError() + } + if bc.isInvalidTimestamp(block) { + return InvalidTimestamp{expectedTimestamp: time.Now().UTC().UnixNano(), actualTimestamp: block.unixTimeStamp}.doError() + } + if bc.isInvalidPreviousHash(block) { + return InvalidPreviousHash{expectedHash: bc.blocks[len(bc.blocks)-1].hash, actualHash: block.previousHash}.doError() + } + if bc.isInvalidGenesisBlock(block) { + return InvalidGenesisBlock{expectedFormat: make([]byte, 32), actualFormat: bc.genesisBlock.hash}.doError() + } + + for _, transaction := range block.transaction { + if err := bc.isInsufficientInputValue(transaction); err != nil { + return err + } + if err := bc.isInputValueLessZero(transaction); err != nil { + return err + } + } + + return nil +} + +func (bc *Blockchain) isInvalidIndex(block Block) bool { + return block.index != len(bc.blocks) +} + +func (bc *Blockchain) isAchronTimestamp(block Block) bool { + return block.unixTimeStamp < bc.blocks[len(bc.blocks)-1].unixTimeStamp +} + +func (bc *Blockchain) isInvalidTimestamp(block Block) bool { + return block.unixTimeStamp > time.Now().UTC().UnixNano() +} + +func (bc *Blockchain) isInvalidPreviousHash(block Block) bool { + return !bytes.Equal(block.previousHash, bc.blocks[len(bc.blocks)-1].hash) +} + +func (bc *Blockchain) isInvalidGenesisBlock(block Block) bool { + return !bytes.Equal(bc.genesisBlock.hash, make([]byte, 32)) +} + +func (bc *Blockchain) isInsufficientInputValue(transaction Transaction) error { + if transaction.inputValue() < transaction.outputValue() { + return InsufficientInputValue{expectedValue: transaction.outputValue(), actualValue: transaction.inputValue()}.doError() + } + + return nil +} + +func (bc *Blockchain) isInputValueLessZero(transaction Transaction) error { + for _, input := range transaction.inputs { + if input.value < 0 { + return InvalidInput{expectedValue: 0, actualValue: input.value}.doError() + } + } + + return nil +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..2b0bb84 --- /dev/null +++ b/errors.go @@ -0,0 +1,160 @@ +package main + +import ( + "errors" + "fmt" +) + +// MismatchedIndex is an error type that is returned when the index of a block +// does not match the index of the previous block. +type MismatchedIndex struct { + expectedIndex int + actualIndex int + statusCode int + err error +} + +func (mi *MismatchedIndex) Error() string { + return fmt.Sprintf("MismatchedIndexError: Expected: %d, Got: %d", mi.expectedIndex, mi.actualIndex) +} + +func (mi MismatchedIndex) doError() error { + return &MismatchedIndex{ + expectedIndex: mi.expectedIndex, + actualIndex: mi.actualIndex, + statusCode: 503, + err: errors.New("MismatchedIndexError"), + } +} + +// AchronologicalTimestamp is an error type that is returned when the timestamp +// of a block is earlier than the timestamp of the previous block. +type AchronologicalTimestamp struct { + expectedTimestamp int64 + actualTimestamp int64 + statusCode int + err error +} + +func (ai *AchronologicalTimestamp) Error() string { + return fmt.Sprintf("AchronologicalTimestampError: Expected: Timestamp > %d, Got: %d", ai.expectedTimestamp, ai.actualTimestamp) +} + +func (ai AchronologicalTimestamp) doError() error { + return &AchronologicalTimestamp{ + expectedTimestamp: ai.expectedTimestamp, + actualTimestamp: ai.actualTimestamp, + statusCode: 503, + err: errors.New("AchronologicalTimestampError"), + } +} + +// InvalidTimestamp is an error type that is returned when the timestamp of a +// block has not happened yet. +type InvalidTimestamp struct { + expectedTimestamp int64 + actualTimestamp int64 + statusCode int + err error +} + +func (it *InvalidTimestamp) Error() string { + return fmt.Sprintf("InvalidTimestampError: Expected: Timestamp < %d, Got: %d", it.expectedTimestamp, it.actualTimestamp) +} + +func (it InvalidTimestamp) doError() error { + return &InvalidTimestamp{ + expectedTimestamp: it.expectedTimestamp, + actualTimestamp: it.actualTimestamp, + statusCode: 503, + err: errors.New("InvalidTimestampError"), + } +} + +// InvalidPreviousHash is an error type that is returned when the previous hash +// of a block does not match the hash of the previous block. +type InvalidPreviousHash struct { + expectedHash []byte + actualHash []byte + statusCode int + err error +} + +func (ip *InvalidPreviousHash) Error() string { + return fmt.Sprintf("InvalidPreviousHashError: Expected: %x, Got: %x", ip.expectedHash, ip.actualHash) +} + +func (ip InvalidPreviousHash) doError() error { + return &InvalidPreviousHash{ + expectedHash: ip.expectedHash, + actualHash: ip.actualHash, + statusCode: 503, + err: errors.New("InvalidPreviousHashError"), + } +} + +// InvalidGenesisBlock is an error type that is returned when the genesis block +// is invalid. +type InvalidGenesisBlock struct { + expectedFormat []byte + actualFormat []byte + statusCode int + err error +} + +func (ig *InvalidGenesisBlock) Error() string { + return fmt.Sprintf("InvalidGenesisBlockError: Expected format: %x, Got: %x", ig.expectedFormat, ig.actualFormat) +} + +func (ig InvalidGenesisBlock) doError() error { + return &InvalidGenesisBlock{ + expectedFormat: ig.expectedFormat, + actualFormat: ig.actualFormat, + statusCode: 503, + err: errors.New("InvalidGenesisBlockError"), + } +} + +// InsufficientInputValue is an error type that is returned when the input value +// of a transaction is less than the minimum value. +type InsufficientInputValue struct { + expectedValue int + actualValue int + statusCode int + err error +} + +func (ii *InsufficientInputValue) Error() string { + return fmt.Sprintf("InsufficientInputValueError: Expected: > %d, Got: %d", ii.expectedValue, ii.actualValue) +} + +func (ii InsufficientInputValue) doError() error { + return &InsufficientInputValue{ + expectedValue: ii.expectedValue, + actualValue: ii.actualValue, + statusCode: 503, + err: errors.New("InsufficientInputValueError"), + } +} + +// InvalidInput is an error type that is returned when the input of a transaction +// is < 0. +type InvalidInput struct { + expectedValue int + actualValue int + statusCode int + err error +} + +func (ii *InvalidInput) Error() string { + return fmt.Sprintf("InvalidInputError: Expected: > %d, Got: %d", ii.expectedValue, ii.actualValue) +} + +func (ii InvalidInput) doError() error { + return &InvalidInput{ + expectedValue: ii.expectedValue, + actualValue: ii.actualValue, + statusCode: 503, + err: errors.New("InvalidInputError"), + } +} diff --git a/main.go b/main.go index 27c742d..4d6be52 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,8 @@ import ( ) func main() { + var bc Blockchain + genesisBlock := Block{ index: 0, unixTimeStamp: time.Now().UTC().UnixNano(), @@ -16,26 +18,38 @@ func main() { transaction: nil, } - blockchain := Blockchain{ - genesisBlock: genesisBlock, - blocks: []Block{genesisBlock}, - } + bc.newBlockchain(genesisBlock) block := newBlock( 1, time.Now().UTC().UnixNano(), genesisBlock.hash, 1, - []Transaction{}, + []Transaction{ + { + inputs: []Output{ + { + index: 0, + address: "Bob", + value: 100000, + }, + }, + outputs: []Output{ + { + index: 0, + address: "Alice", + value: 1, + }, + }, + }, + }, ) block.mine() - fmt.Println(block) + //fmt.Println(block) - fmt.Println(blockchain) -} + bc.addBlock(*block) -func test() string { - return "Hello, world." + fmt.Println(bc) } diff --git a/output.go b/output.go index eb393ae..ef9804d 100644 --- a/output.go +++ b/output.go @@ -1,6 +1,7 @@ package main type Output struct { + index int address string value int } diff --git a/transaction.go b/transaction.go index a9afad9..4b79076 100644 --- a/transaction.go +++ b/transaction.go @@ -4,3 +4,23 @@ type Transaction struct { inputs []Output outputs []Output } + +func (t *Transaction) inputValue() int { + var total int + + for _, input := range t.inputs { + total += input.value + } + + return total +} + +func (t *Transaction) outputValue() int { + var total int + + for _, output := range t.outputs { + total += output.value + } + + return total +} From c3a9488c7534fd96daaf907551de69516f4d4b55 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Fri, 1 Jul 2022 16:21:02 +0100 Subject: [PATCH 05/13] Implement logging basics --- .github/workflows/coverage_go.yml | 58 +++++++++++++++++++++ .gitignore | 3 ++ block.go | 4 ++ blockchain.go | 7 +-- errors.go | 2 +- main.go | 84 ++++++++++++++++++++++--------- 6 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/coverage_go.yml diff --git a/.github/workflows/coverage_go.yml b/.github/workflows/coverage_go.yml new file mode 100644 index 0000000..ca74f75 --- /dev/null +++ b/.github/workflows/coverage_go.yml @@ -0,0 +1,58 @@ +on: + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + name: Update coverage badge + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + + - name: Setup go + uses: actions/setup-go@v2 + with: + go-version: '1.14.4' + + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Run Test + run: | + go test -v ./... -covermode=count -coverprofile=coverage.out + go tool cover -func=coverage.out -o=coverage.out + + - name: Go Coverage Badge # Pass the `coverage.out` output to this action + uses: tj-actions/coverage-badge-go@v1.2 + with: + filename: coverage.out + + - name: Verify Changed files + uses: tj-actions/verify-changed-files@v9.1 + id: verify-changed-files + with: + files: README.md + + - name: Commit changes + if: steps.verify-changed-files.outputs.files_changed == 'true' + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add README.md + git commit -m "chore: Updated coverage badge." + + - name: Push changes + if: steps.verify-changed-files.outputs.files_changed == 'true' + uses: ad-m/github-push-action@master + with: + github_token: ${{ github.token }} + branch: ${{ github.head_ref }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 66fd13c..824fac2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Logs +logs.txt + # Binaries for programs and plugins *.exe *.exe~ diff --git a/block.go b/block.go index 376c457..132c598 100644 --- a/block.go +++ b/block.go @@ -28,6 +28,8 @@ func newBlock(index int, unixTimeStamp int64, previousHash []byte, difficulty in transaction: transaction, } + blocksLogger.Printf("Block Initialised: %v\n", b) + b.mine() return b @@ -38,6 +40,8 @@ func (b *Block) mine() { b.nonce++ b.hash = b.calculateHash() } + + blocksLogger.Printf("Block Mined: %v\n", b) } func (b *Block) calculateHash() []byte { diff --git a/blockchain.go b/blockchain.go index fc8a060..070c6fe 100644 --- a/blockchain.go +++ b/blockchain.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "fmt" "time" ) @@ -14,11 +13,13 @@ type Blockchain struct { func (bc *Blockchain) newBlockchain(genesisBlock Block) { bc.genesisBlock = genesisBlock bc.blocks = []Block{genesisBlock} + + infoLogger.Printf("New blockchain created with genesis block: %s", bc.genesisBlock.hash) } func (bc *Blockchain) addBlock(block Block) error { if err := bc.isValidBlock(block); err != nil { - fmt.Printf(err.Error()) + warningLogger.Printf(err.Error()) return err } @@ -78,7 +79,7 @@ func (bc *Blockchain) isInvalidGenesisBlock(block Block) bool { func (bc *Blockchain) isInsufficientInputValue(transaction Transaction) error { if transaction.inputValue() < transaction.outputValue() { - return InsufficientInputValue{expectedValue: transaction.outputValue(), actualValue: transaction.inputValue()}.doError() + return InsufficientInputValue{expectedValue: transaction.inputValue(), actualValue: transaction.outputValue()}.doError() } return nil diff --git a/errors.go b/errors.go index 2b0bb84..6e62d01 100644 --- a/errors.go +++ b/errors.go @@ -125,7 +125,7 @@ type InsufficientInputValue struct { } func (ii *InsufficientInputValue) Error() string { - return fmt.Sprintf("InsufficientInputValueError: Expected: > %d, Got: %d", ii.expectedValue, ii.actualValue) + return fmt.Sprintf("InsufficientInputValueError: Expected: <= %d, Got: %d", ii.expectedValue, ii.actualValue) } func (ii InsufficientInputValue) doError() error { diff --git a/main.go b/main.go index 4d6be52..01b35e4 100644 --- a/main.go +++ b/main.go @@ -1,23 +1,40 @@ package main import ( - "fmt" + "io" + "log" + "os" "time" ) -func main() { - var bc Blockchain +var ( + bc Blockchain + infoLogger *log.Logger + errorLogger *log.Logger + warningLogger *log.Logger + blocksLogger *log.Logger +) - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, +func init() { + file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) + + if err != nil { + errorLogger.Fatal(err) } + multiWriter := io.MultiWriter(os.Stdout, file) + + infoLogger = log.New(multiWriter, "INFO: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) + warningLogger = log.New(multiWriter, "WARNING: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) + errorLogger = log.New(multiWriter, "ERROR: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) + blocksLogger = log.New(multiWriter, "BLOCKS: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) + + infoLogger.Println("Starting MVInventoryChain") +} + +func main() { + genesisBlock := initGenesis() + genesisBlock.mine() bc.newBlockchain(genesisBlock) block := newBlock( @@ -27,18 +44,12 @@ func main() { 1, []Transaction{ { - inputs: []Output{ - { - index: 0, - address: "Bob", - value: 100000, - }, - }, + inputs: []Output{bc.blocks[0].transaction[0].outputs[1]}, outputs: []Output{ { index: 0, address: "Alice", - value: 1, + value: 2, }, }, }, @@ -46,10 +57,35 @@ func main() { ) block.mine() + if bc.addBlock(*block) != nil { + errorLogger.Println("Error adding block") + } +} - //fmt.Println(block) - - bc.addBlock(*block) - - fmt.Println(bc) +func initGenesis() Block { + return Block{ + index: 0, + unixTimeStamp: time.Now().UTC().UnixNano(), + hash: make([]byte, 32), + previousHash: nil, + nonce: 0, + difficulty: 0, + transaction: []Transaction{ + { + inputs: make([]Output, 0), + outputs: []Output{ + { + index: 0, + address: "Alice", + value: 30, + }, + { + index: 1, + address: "Bob", + value: 7, + }, + }, + }, + }, + } } From aad2033cfbbf9b848c448a318e2df1030755ff45 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Fri, 1 Jul 2022 16:25:10 +0100 Subject: [PATCH 06/13] Fix unchecked bc.addBlock errors --- block_errors_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/block_errors_test.go b/block_errors_test.go index 63f52b1..753c566 100644 --- a/block_errors_test.go +++ b/block_errors_test.go @@ -305,7 +305,9 @@ func TestNewBlockInvalidPreviousHash(t *testing.T) { block.mine() - bc.addBlock(*block) + if bc.addBlock(*block) != nil { + t.Errorf("Unexpected error") + } block2 := newBlock( 2, @@ -375,7 +377,9 @@ func TestValidPreviousHash(t *testing.T) { block.mine() - bc.addBlock(*block) + if bc.addBlock(*block) != nil { + t.Errorf("Unexpected error") + } block2 := newBlock( 2, From 8ec81a3fd2cbf2dc5dba863967e85c58ad18a687 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Fri, 1 Jul 2022 17:56:47 +0100 Subject: [PATCH 07/13] Reformatted project --- block.go | 63 --- block_errors_test.go | 596 ------------------- blockchain.go | 96 ---- blockchain/block.go | 65 +++ blockchain/blockchain.go | 99 ++++ blockchain/output.go | 7 + transaction.go => blockchain/transaction.go | 14 +- errors.go => customErrors/customErrors.go | 86 +-- go.mod | 2 +- logging/logging.go | 29 + main.go | 87 ++- output.go | 7 - test/block_errors_test.go | 598 ++++++++++++++++++++ 13 files changed, 885 insertions(+), 864 deletions(-) delete mode 100644 block.go delete mode 100644 block_errors_test.go delete mode 100644 blockchain.go create mode 100644 blockchain/block.go create mode 100644 blockchain/blockchain.go create mode 100644 blockchain/output.go rename transaction.go => blockchain/transaction.go (51%) rename errors.go => customErrors/customErrors.go (67%) create mode 100644 logging/logging.go delete mode 100644 output.go create mode 100644 test/block_errors_test.go diff --git a/block.go b/block.go deleted file mode 100644 index 132c598..0000000 --- a/block.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "crypto/sha256" - "encoding/json" - "strconv" - "strings" -) - -type Block struct { - index int - unixTimeStamp int64 - hash []byte - previousHash []byte - nonce int - difficulty int - transaction []Transaction -} - -func newBlock(index int, unixTimeStamp int64, previousHash []byte, difficulty int, transaction []Transaction) *Block { - b := &Block{ - index: index, - unixTimeStamp: unixTimeStamp, - hash: make([]byte, 32), - previousHash: previousHash, - nonce: 0, - difficulty: difficulty, - transaction: transaction, - } - - blocksLogger.Printf("Block Initialised: %v\n", b) - - b.mine() - - return b -} - -func (b *Block) mine() { - for !b.validateHash(b.calculateHash()) { - b.nonce++ - b.hash = b.calculateHash() - } - - blocksLogger.Printf("Block Mined: %v\n", b) -} - -func (b *Block) calculateHash() []byte { - hash := sha256.Sum256([]byte(strconv.Itoa(b.index) + strconv.FormatInt(b.unixTimeStamp, 10) + string(b.previousHash) + strconv.Itoa(b.nonce) + strconv.Itoa(b.difficulty) + transactionToString(b.transaction))) - return hash[:] -} - -func (b *Block) validateHash(hash []byte) bool { - prefix := strings.Repeat("0", b.difficulty) - return strings.HasPrefix(string(hash), prefix) -} - -func first(n []byte, _ error) []byte { - return n -} - -func transactionToString(transaction []Transaction) string { - return string(first(json.Marshal(transaction))) -} diff --git a/block_errors_test.go b/block_errors_test.go deleted file mode 100644 index 753c566..0000000 --- a/block_errors_test.go +++ /dev/null @@ -1,596 +0,0 @@ -package main - -import ( - "testing" - "time" -) - -// func BenchmarkTest(b *testing.B) { -// for i := 0; i < b.N; i++ { -// test() -// } -// } - -// func ExampleMain() { -// main() -// // Output: Hello, world. -// } - -// MismatchedIndex Tests -func TestNewBlockMismatchedIndexLess(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - -1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestNewBlockMismatchedIndexGreater(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 2, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestNewBlockMismatchedIndexEqual(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 0, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestNewBlockMismatchedIndex(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) != nil { - t.Errorf("Expected false, got true") - } -} - -// AchronologicalTimeStamp Test -func TestNewBlockAchronologicalTimeStamp(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - 10, - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestNewBlockChronologicalTimeStamp(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) != nil { - t.Errorf("Expected false, got true") - } -} - -// InvalidTimestamp Test -func TestNewBlockInvalidTimestamp(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano()+1000000000000000000, - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestNewBlockValidTimestamp(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) != nil { - t.Errorf("Expected false, got true") - } -} - -// InvalidPreviousHash Test -func TestNewBlockInvalidPreviousHashGenesis(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestNewBlockInvalidPreviousHash(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) != nil { - t.Errorf("Unexpected error") - } - - block2 := newBlock( - 2, - time.Now().UTC().UnixNano(), - []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - 1, - []Transaction{}, - ) - - block2.mine() - - if bc.addBlock(*block2) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestValidPreviousHashGenesis(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) != nil { - t.Errorf("Expected false, got true") - } -} - -func TestValidPreviousHash(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) != nil { - t.Errorf("Unexpected error") - } - - block2 := newBlock( - 2, - time.Now().UTC().UnixNano(), - block.hash, - 1, - []Transaction{}, - ) - - block2.mine() - - if bc.addBlock(*block2) != nil { - t.Errorf("Expected false, got true") - } -} - -// InvalidGenesisBlock Test -func TestNewBlockInvalidGenesisBlock(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: []byte{1, 2, 3, 4, 5, 6}, - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestNewBlockValidGenesisBlock(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{}, - ) - - block.mine() - - if bc.addBlock(*block) != nil { - t.Errorf("Expected false, got true") - } -} - -// InsufficientInputValue Test -func TestNewBlockInsufficientInputValue(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{ - { - inputs: []Output{ - { - index: 0, - address: "Bob", - value: 1, - }, - }, - outputs: []Output{ - { - index: 0, - address: "Alice", - value: 2, - }, - }, - }, - }, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} - -func TestNewBlockSufficientInputValue(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{ - { - inputs: []Output{ - { - index: 0, - address: "Bob", - value: 1, - }, - }, - outputs: []Output{ - { - index: 0, - address: "Alice", - value: 1, - }, - }, - }, - }, - ) - - block.mine() - - if bc.addBlock(*block) != nil { - t.Errorf("Expected false, got true") - } -} - -// InvalidInput Test -func TestNewBlockInvalidInput(t *testing.T) { - genesisBlock := Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: nil, - } - - bc := Blockchain{} - bc.newBlockchain(genesisBlock) - - block := newBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.hash, - 1, - []Transaction{ - { - inputs: []Output{ - { - index: 0, - address: "Bob", - value: -1, - }, - }, - outputs: []Output{ - { - index: 0, - address: "Alice", - value: -1, - }, - }, - }, - }, - ) - - block.mine() - - if bc.addBlock(*block) == nil { - t.Errorf("Expected false, got true") - } -} diff --git a/blockchain.go b/blockchain.go deleted file mode 100644 index 070c6fe..0000000 --- a/blockchain.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "bytes" - "time" -) - -type Blockchain struct { - genesisBlock Block - blocks []Block -} - -func (bc *Blockchain) newBlockchain(genesisBlock Block) { - bc.genesisBlock = genesisBlock - bc.blocks = []Block{genesisBlock} - - infoLogger.Printf("New blockchain created with genesis block: %s", bc.genesisBlock.hash) -} - -func (bc *Blockchain) addBlock(block Block) error { - if err := bc.isValidBlock(block); err != nil { - warningLogger.Printf(err.Error()) - return err - } - - bc.blocks = append(bc.blocks, block) - - return nil -} - -func (bc *Blockchain) isValidBlock(block Block) error { - if bc.isInvalidIndex(block) { - return MismatchedIndex{expectedIndex: len(bc.blocks), actualIndex: block.index}.doError() - } - if bc.isAchronTimestamp(block) { - return AchronologicalTimestamp{expectedTimestamp: bc.blocks[len(bc.blocks)-1].unixTimeStamp, actualTimestamp: block.unixTimeStamp}.doError() - } - if bc.isInvalidTimestamp(block) { - return InvalidTimestamp{expectedTimestamp: time.Now().UTC().UnixNano(), actualTimestamp: block.unixTimeStamp}.doError() - } - if bc.isInvalidPreviousHash(block) { - return InvalidPreviousHash{expectedHash: bc.blocks[len(bc.blocks)-1].hash, actualHash: block.previousHash}.doError() - } - if bc.isInvalidGenesisBlock(block) { - return InvalidGenesisBlock{expectedFormat: make([]byte, 32), actualFormat: bc.genesisBlock.hash}.doError() - } - - for _, transaction := range block.transaction { - if err := bc.isInsufficientInputValue(transaction); err != nil { - return err - } - if err := bc.isInputValueLessZero(transaction); err != nil { - return err - } - } - - return nil -} - -func (bc *Blockchain) isInvalidIndex(block Block) bool { - return block.index != len(bc.blocks) -} - -func (bc *Blockchain) isAchronTimestamp(block Block) bool { - return block.unixTimeStamp < bc.blocks[len(bc.blocks)-1].unixTimeStamp -} - -func (bc *Blockchain) isInvalidTimestamp(block Block) bool { - return block.unixTimeStamp > time.Now().UTC().UnixNano() -} - -func (bc *Blockchain) isInvalidPreviousHash(block Block) bool { - return !bytes.Equal(block.previousHash, bc.blocks[len(bc.blocks)-1].hash) -} - -func (bc *Blockchain) isInvalidGenesisBlock(block Block) bool { - return !bytes.Equal(bc.genesisBlock.hash, make([]byte, 32)) -} - -func (bc *Blockchain) isInsufficientInputValue(transaction Transaction) error { - if transaction.inputValue() < transaction.outputValue() { - return InsufficientInputValue{expectedValue: transaction.inputValue(), actualValue: transaction.outputValue()}.doError() - } - - return nil -} - -func (bc *Blockchain) isInputValueLessZero(transaction Transaction) error { - for _, input := range transaction.inputs { - if input.value < 0 { - return InvalidInput{expectedValue: 0, actualValue: input.value}.doError() - } - } - - return nil -} diff --git a/blockchain/block.go b/blockchain/block.go new file mode 100644 index 0000000..0d8fef4 --- /dev/null +++ b/blockchain/block.go @@ -0,0 +1,65 @@ +package blockchain + +import ( + "crypto/sha256" + "encoding/json" + "strconv" + "strings" + + logging "github.com/MVRetailManager/MVInventoryChain/logging" +) + +type Block struct { + Index int + UnixTimeStamp int64 + Hash []byte + PreviousHash []byte + Nonce int + Difficulty int + Transaction []Transaction +} + +func NewBlock(index int, unixTimeStamp int64, previousHash []byte, difficulty int, transaction []Transaction) *Block { + b := &Block{ + Index: index, + UnixTimeStamp: unixTimeStamp, + Hash: make([]byte, 32), + PreviousHash: previousHash, + Nonce: 0, + Difficulty: difficulty, + Transaction: transaction, + } + + logging.BlocksLogger.Printf("Block Initialised: %v\n", b) + + b.Mine() + + return b +} + +func (b *Block) Mine() { + for !b.validateHash(b.calculateHash()) { + b.Nonce++ + b.Hash = b.calculateHash() + } + + logging.BlocksLogger.Printf("Block Mined: %v\n", b) +} + +func (b *Block) calculateHash() []byte { + hash := sha256.Sum256([]byte(strconv.Itoa(b.Index) + strconv.FormatInt(b.UnixTimeStamp, 10) + string(b.PreviousHash) + strconv.Itoa(b.Nonce) + strconv.Itoa(b.Difficulty) + transactionToString(b.Transaction))) + return hash[:] +} + +func (b *Block) validateHash(hash []byte) bool { + prefix := strings.Repeat("0", b.Difficulty) + return strings.HasPrefix(string(hash), prefix) +} + +func first(n []byte, _ error) []byte { + return n +} + +func transactionToString(transaction []Transaction) string { + return string(first(json.Marshal(transaction))) +} diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go new file mode 100644 index 0000000..4885d15 --- /dev/null +++ b/blockchain/blockchain.go @@ -0,0 +1,99 @@ +package blockchain + +import ( + "bytes" + "time" + + bcErrors "github.com/MVRetailManager/MVInventoryChain/customErrors" + logging "github.com/MVRetailManager/MVInventoryChain/logging" +) + +type Blockchain struct { + GenesisBlock Block + Blocks []Block +} + +func (bc *Blockchain) NewBlockchain(genesisBlock Block) { + bc.GenesisBlock = genesisBlock + bc.Blocks = []Block{genesisBlock} + + logging.InfoLogger.Printf("New blockchain created with genesis block: %s", bc.GenesisBlock.Hash) +} + +func (bc *Blockchain) AddBlock(block Block) error { + if err := bc.isValidBlock(block); err != nil { + logging.WarningLogger.Printf(err.Error()) + return err + } + + bc.Blocks = append(bc.Blocks, block) + + return nil +} + +func (bc *Blockchain) isValidBlock(block Block) error { + if bc.isInvalidIndex(block) { + return bcErrors.MismatchedIndex{ExpectedIndex: len(bc.Blocks), ActualIndex: block.Index}.DoError() + } + if bc.isAchronTimestamp(block) { + return bcErrors.AchronologicalTimestamp{ExpectedTimestamp: bc.Blocks[len(bc.Blocks)-1].UnixTimeStamp, ActualTimestamp: block.UnixTimeStamp}.DoError() + } + if bc.isInvalidTimestamp(block) { + return bcErrors.InvalidTimestamp{ExpectedTimestamp: time.Now().UTC().UnixNano(), ActualTimestamp: block.UnixTimeStamp}.DoError() + } + if bc.isInvalidPreviousHash(block) { + return bcErrors.InvalidPreviousHash{ExpectedHash: bc.Blocks[len(bc.Blocks)-1].Hash, ActualHash: block.PreviousHash}.DoError() + } + if bc.isInvalidGenesisBlock(block) { + return bcErrors.InvalidGenesisBlock{ExpectedFormat: make([]byte, 32), ActualFormat: bc.GenesisBlock.Hash}.DoError() + } + + for _, transaction := range block.Transaction { + if err := bc.isInsufficientInputValue(transaction); err != nil { + return err + } + if err := bc.isInputValueLessZero(transaction); err != nil { + return err + } + } + + return nil +} + +func (bc *Blockchain) isInvalidIndex(block Block) bool { + return block.Index != len(bc.Blocks) +} + +func (bc *Blockchain) isAchronTimestamp(block Block) bool { + return block.UnixTimeStamp < bc.Blocks[len(bc.Blocks)-1].UnixTimeStamp +} + +func (bc *Blockchain) isInvalidTimestamp(block Block) bool { + return block.UnixTimeStamp > time.Now().UTC().UnixNano() +} + +func (bc *Blockchain) isInvalidPreviousHash(block Block) bool { + return !bytes.Equal(block.PreviousHash, bc.Blocks[len(bc.Blocks)-1].Hash) +} + +func (bc *Blockchain) isInvalidGenesisBlock(block Block) bool { + return !bytes.Equal(bc.GenesisBlock.Hash, make([]byte, 32)) +} + +func (bc *Blockchain) isInsufficientInputValue(transaction Transaction) error { + if transaction.inputValue() < transaction.outputValue() { + return bcErrors.InsufficientInputValue{ExpectedValue: transaction.inputValue(), ActualValue: transaction.outputValue()}.DoError() + } + + return nil +} + +func (bc *Blockchain) isInputValueLessZero(transaction Transaction) error { + for _, input := range transaction.Inputs { + if input.Value < 0 { + return bcErrors.InvalidInput{ExpectedValue: 0, ActualValue: input.Value}.DoError() + } + } + + return nil +} diff --git a/blockchain/output.go b/blockchain/output.go new file mode 100644 index 0000000..038c46f --- /dev/null +++ b/blockchain/output.go @@ -0,0 +1,7 @@ +package blockchain + +type Output struct { + Index int + Address string + Value int +} diff --git a/transaction.go b/blockchain/transaction.go similarity index 51% rename from transaction.go rename to blockchain/transaction.go index 4b79076..aa7b3b1 100644 --- a/transaction.go +++ b/blockchain/transaction.go @@ -1,15 +1,15 @@ -package main +package blockchain type Transaction struct { - inputs []Output - outputs []Output + Inputs []Output + Outputs []Output } func (t *Transaction) inputValue() int { var total int - for _, input := range t.inputs { - total += input.value + for _, input := range t.Inputs { + total += input.Value } return total @@ -18,8 +18,8 @@ func (t *Transaction) inputValue() int { func (t *Transaction) outputValue() int { var total int - for _, output := range t.outputs { - total += output.value + for _, output := range t.Outputs { + total += output.Value } return total diff --git a/errors.go b/customErrors/customErrors.go similarity index 67% rename from errors.go rename to customErrors/customErrors.go index 6e62d01..d3f7de6 100644 --- a/errors.go +++ b/customErrors/customErrors.go @@ -1,4 +1,4 @@ -package main +package customErrors import ( "errors" @@ -8,20 +8,20 @@ import ( // MismatchedIndex is an error type that is returned when the index of a block // does not match the index of the previous block. type MismatchedIndex struct { - expectedIndex int - actualIndex int + ExpectedIndex int + ActualIndex int statusCode int err error } func (mi *MismatchedIndex) Error() string { - return fmt.Sprintf("MismatchedIndexError: Expected: %d, Got: %d", mi.expectedIndex, mi.actualIndex) + return fmt.Sprintf("MismatchedIndexError: Expected: %d, Got: %d", mi.ExpectedIndex, mi.ActualIndex) } -func (mi MismatchedIndex) doError() error { +func (mi MismatchedIndex) DoError() error { return &MismatchedIndex{ - expectedIndex: mi.expectedIndex, - actualIndex: mi.actualIndex, + ExpectedIndex: mi.ExpectedIndex, + ActualIndex: mi.ActualIndex, statusCode: 503, err: errors.New("MismatchedIndexError"), } @@ -30,20 +30,20 @@ func (mi MismatchedIndex) doError() error { // AchronologicalTimestamp is an error type that is returned when the timestamp // of a block is earlier than the timestamp of the previous block. type AchronologicalTimestamp struct { - expectedTimestamp int64 - actualTimestamp int64 + ExpectedTimestamp int64 + ActualTimestamp int64 statusCode int err error } func (ai *AchronologicalTimestamp) Error() string { - return fmt.Sprintf("AchronologicalTimestampError: Expected: Timestamp > %d, Got: %d", ai.expectedTimestamp, ai.actualTimestamp) + return fmt.Sprintf("AchronologicalTimestampError: Expected: Timestamp > %d, Got: %d", ai.ExpectedTimestamp, ai.ActualTimestamp) } -func (ai AchronologicalTimestamp) doError() error { +func (ai AchronologicalTimestamp) DoError() error { return &AchronologicalTimestamp{ - expectedTimestamp: ai.expectedTimestamp, - actualTimestamp: ai.actualTimestamp, + ExpectedTimestamp: ai.ExpectedTimestamp, + ActualTimestamp: ai.ActualTimestamp, statusCode: 503, err: errors.New("AchronologicalTimestampError"), } @@ -52,20 +52,20 @@ func (ai AchronologicalTimestamp) doError() error { // InvalidTimestamp is an error type that is returned when the timestamp of a // block has not happened yet. type InvalidTimestamp struct { - expectedTimestamp int64 - actualTimestamp int64 + ExpectedTimestamp int64 + ActualTimestamp int64 statusCode int err error } func (it *InvalidTimestamp) Error() string { - return fmt.Sprintf("InvalidTimestampError: Expected: Timestamp < %d, Got: %d", it.expectedTimestamp, it.actualTimestamp) + return fmt.Sprintf("InvalidTimestampError: Expected: Timestamp < %d, Got: %d", it.ExpectedTimestamp, it.ActualTimestamp) } -func (it InvalidTimestamp) doError() error { +func (it InvalidTimestamp) DoError() error { return &InvalidTimestamp{ - expectedTimestamp: it.expectedTimestamp, - actualTimestamp: it.actualTimestamp, + ExpectedTimestamp: it.ExpectedTimestamp, + ActualTimestamp: it.ActualTimestamp, statusCode: 503, err: errors.New("InvalidTimestampError"), } @@ -74,20 +74,20 @@ func (it InvalidTimestamp) doError() error { // InvalidPreviousHash is an error type that is returned when the previous hash // of a block does not match the hash of the previous block. type InvalidPreviousHash struct { - expectedHash []byte - actualHash []byte + ExpectedHash []byte + ActualHash []byte statusCode int err error } func (ip *InvalidPreviousHash) Error() string { - return fmt.Sprintf("InvalidPreviousHashError: Expected: %x, Got: %x", ip.expectedHash, ip.actualHash) + return fmt.Sprintf("InvalidPreviousHashError: Expected: %x, Got: %x", ip.ExpectedHash, ip.ActualHash) } -func (ip InvalidPreviousHash) doError() error { +func (ip InvalidPreviousHash) DoError() error { return &InvalidPreviousHash{ - expectedHash: ip.expectedHash, - actualHash: ip.actualHash, + ExpectedHash: ip.ExpectedHash, + ActualHash: ip.ActualHash, statusCode: 503, err: errors.New("InvalidPreviousHashError"), } @@ -96,20 +96,20 @@ func (ip InvalidPreviousHash) doError() error { // InvalidGenesisBlock is an error type that is returned when the genesis block // is invalid. type InvalidGenesisBlock struct { - expectedFormat []byte - actualFormat []byte + ExpectedFormat []byte + ActualFormat []byte statusCode int err error } func (ig *InvalidGenesisBlock) Error() string { - return fmt.Sprintf("InvalidGenesisBlockError: Expected format: %x, Got: %x", ig.expectedFormat, ig.actualFormat) + return fmt.Sprintf("InvalidGenesisBlockError: Expected format: %x, Got: %x", ig.ExpectedFormat, ig.ActualFormat) } -func (ig InvalidGenesisBlock) doError() error { +func (ig InvalidGenesisBlock) DoError() error { return &InvalidGenesisBlock{ - expectedFormat: ig.expectedFormat, - actualFormat: ig.actualFormat, + ExpectedFormat: ig.ExpectedFormat, + ActualFormat: ig.ActualFormat, statusCode: 503, err: errors.New("InvalidGenesisBlockError"), } @@ -118,20 +118,20 @@ func (ig InvalidGenesisBlock) doError() error { // InsufficientInputValue is an error type that is returned when the input value // of a transaction is less than the minimum value. type InsufficientInputValue struct { - expectedValue int - actualValue int + ExpectedValue int + ActualValue int statusCode int err error } func (ii *InsufficientInputValue) Error() string { - return fmt.Sprintf("InsufficientInputValueError: Expected: <= %d, Got: %d", ii.expectedValue, ii.actualValue) + return fmt.Sprintf("InsufficientInputValueError: Expected: <= %d, Got: %d", ii.ExpectedValue, ii.ActualValue) } -func (ii InsufficientInputValue) doError() error { +func (ii InsufficientInputValue) DoError() error { return &InsufficientInputValue{ - expectedValue: ii.expectedValue, - actualValue: ii.actualValue, + ExpectedValue: ii.ExpectedValue, + ActualValue: ii.ActualValue, statusCode: 503, err: errors.New("InsufficientInputValueError"), } @@ -140,20 +140,20 @@ func (ii InsufficientInputValue) doError() error { // InvalidInput is an error type that is returned when the input of a transaction // is < 0. type InvalidInput struct { - expectedValue int - actualValue int + ExpectedValue int + ActualValue int statusCode int err error } func (ii *InvalidInput) Error() string { - return fmt.Sprintf("InvalidInputError: Expected: > %d, Got: %d", ii.expectedValue, ii.actualValue) + return fmt.Sprintf("InvalidInputError: Expected: > %d, Got: %d", ii.ExpectedValue, ii.ActualValue) } -func (ii InvalidInput) doError() error { +func (ii InvalidInput) DoError() error { return &InvalidInput{ - expectedValue: ii.expectedValue, - actualValue: ii.actualValue, + ExpectedValue: ii.ExpectedValue, + ActualValue: ii.ActualValue, statusCode: 503, err: errors.New("InvalidInputError"), } diff --git a/go.mod b/go.mod index ebf7611..3d51aec 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module mvinvchain +module github.com/MVRetailManager/MVInventoryChain go 1.18 diff --git a/logging/logging.go b/logging/logging.go new file mode 100644 index 0000000..50bf05f --- /dev/null +++ b/logging/logging.go @@ -0,0 +1,29 @@ +package logging + +import ( + "io" + "log" + "os" +) + +var ( + InfoLogger *log.Logger + ErrorLogger *log.Logger + WarningLogger *log.Logger + BlocksLogger *log.Logger +) + +func SetupLogger() { + file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) + + if err != nil { + ErrorLogger.Fatal(err) + } + + multiWriter := io.MultiWriter(os.Stdout, file) + + InfoLogger = log.New(multiWriter, "INFO: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) + WarningLogger = log.New(multiWriter, "WARNING: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) + ErrorLogger = log.New(multiWriter, "ERROR: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) + BlocksLogger = log.New(multiWriter, "BLOCKS: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) +} diff --git a/main.go b/main.go index 01b35e4..ac96aa8 100644 --- a/main.go +++ b/main.go @@ -1,88 +1,73 @@ package main import ( - "io" - "log" - "os" "time" + + blockchainPKG "github.com/MVRetailManager/MVInventoryChain/blockchain" + logging "github.com/MVRetailManager/MVInventoryChain/logging" ) var ( - bc Blockchain - infoLogger *log.Logger - errorLogger *log.Logger - warningLogger *log.Logger - blocksLogger *log.Logger + bc blockchainPKG.Blockchain ) func init() { - file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) - - if err != nil { - errorLogger.Fatal(err) - } - - multiWriter := io.MultiWriter(os.Stdout, file) - - infoLogger = log.New(multiWriter, "INFO: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) - warningLogger = log.New(multiWriter, "WARNING: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) - errorLogger = log.New(multiWriter, "ERROR: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) - blocksLogger = log.New(multiWriter, "BLOCKS: ", log.LstdFlags|log.Lshortfile|log.LUTC|log.Lmicroseconds) + logging.SetupLogger() - infoLogger.Println("Starting MVInventoryChain") + logging.InfoLogger.Println("Starting MVInventoryChain...") } func main() { genesisBlock := initGenesis() - genesisBlock.mine() - bc.newBlockchain(genesisBlock) + genesisBlock.Mine() + bc.NewBlockchain(genesisBlock) - block := newBlock( + block := blockchainPKG.NewBlock( 1, time.Now().UTC().UnixNano(), - genesisBlock.hash, + genesisBlock.Hash, 1, - []Transaction{ + []blockchainPKG.Transaction{ { - inputs: []Output{bc.blocks[0].transaction[0].outputs[1]}, - outputs: []Output{ + Inputs: []blockchainPKG.Output{bc.Blocks[0].Transaction[0].Outputs[1]}, + Outputs: []blockchainPKG.Output{ { - index: 0, - address: "Alice", - value: 2, + Index: 0, + Address: "Alice", + Value: 2, }, }, }, }, ) - block.mine() - if bc.addBlock(*block) != nil { - errorLogger.Println("Error adding block") + block.Mine() + if bc.AddBlock(*block) != nil { + logging.ErrorLogger.Println("Error adding block") } } -func initGenesis() Block { - return Block{ - index: 0, - unixTimeStamp: time.Now().UTC().UnixNano(), - hash: make([]byte, 32), - previousHash: nil, - nonce: 0, - difficulty: 0, - transaction: []Transaction{ +func initGenesis() blockchainPKG.Block { + return blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: []blockchainPKG.Transaction{ { - inputs: make([]Output, 0), - outputs: []Output{ + Inputs: make([]blockchainPKG.Output, 0), + Outputs: []blockchainPKG.Output{ { - index: 0, - address: "Alice", - value: 30, + Index: 0, + Address: "Alice", + Value: 30, }, { - index: 1, - address: "Bob", - value: 7, + Index: 1, + Address: "Bob", + Value: 7, }, }, }, diff --git a/output.go b/output.go deleted file mode 100644 index ef9804d..0000000 --- a/output.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -type Output struct { - index int - address string - value int -} diff --git a/test/block_errors_test.go b/test/block_errors_test.go new file mode 100644 index 0000000..a78dc10 --- /dev/null +++ b/test/block_errors_test.go @@ -0,0 +1,598 @@ +package test + +import ( + "testing" + "time" + + blockchainPKG "github.com/MVRetailManager/MVInventoryChain/blockchain" +) + +// func BenchmarkTest(b *testing.B) { +// for i := 0; i < b.N; i++ { +// test() +// } +// } + +// func ExampleMain() { +// main() +// // Output: Hello, world. +// } + +// MismatchedIndex Tests +func TestNewBlockMismatchedIndexLess(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + -1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockMismatchedIndexGreater(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 2, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockMismatchedIndexEqual(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 0, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockMismatchedIndex(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// AchronologicalTimeStamp Test +func TestNewBlockAchronologicalTimeStamp(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + 10, + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockChronologicalTimeStamp(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// InvalidTimestamp Test +func TestNewBlockInvalidTimestamp(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano()+1000000000000000000, + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockValidTimestamp(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// InvalidPreviousHash Test +func TestNewBlockInvalidPreviousHashGenesis(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockInvalidPreviousHash(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) != nil { + t.Errorf("Unexpected error") + } + + block2 := blockchainPKG.NewBlock( + 2, + time.Now().UTC().UnixNano(), + []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + 1, + []blockchainPKG.Transaction{}, + ) + + block2.Mine() + + if bc.AddBlock(*block2) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestValidPreviousHashGenesis(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +func TestValidPreviousHash(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) != nil { + t.Errorf("Unexpected error") + } + + block2 := blockchainPKG.NewBlock( + 2, + time.Now().UTC().UnixNano(), + block.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block2.Mine() + + if bc.AddBlock(*block2) != nil { + t.Errorf("Expected false, got true") + } +} + +// InvalidGenesisBlock Test +func TestNewBlockInvalidGenesisBlock(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: []byte{1, 2, 3, 4, 5, 6}, + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockValidGenesisBlock(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{}, + ) + + block.Mine() + + if bc.AddBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// InsufficientInputValue Test +func TestNewBlockInsufficientInputValue(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{ + { + Inputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Bob", + Value: 1, + }, + }, + Outputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Alice", + Value: 2, + }, + }, + }, + }, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} + +func TestNewBlockSufficientInputValue(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{ + { + Inputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Bob", + Value: 1, + }, + }, + Outputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Alice", + Value: 1, + }, + }, + }, + }, + ) + + block.Mine() + + if bc.AddBlock(*block) != nil { + t.Errorf("Expected false, got true") + } +} + +// InvalidInput Test +func TestNewBlockInvalidInput(t *testing.T) { + genesisBlock := blockchainPKG.Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: nil, + } + + bc := blockchainPKG.Blockchain{} + bc.NewBlockchain(genesisBlock) + + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{ + { + Inputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Bob", + Value: -1, + }, + }, + Outputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Alice", + Value: -1, + }, + }, + }, + }, + ) + + block.Mine() + + if bc.AddBlock(*block) == nil { + t.Errorf("Expected false, got true") + } +} From 059a898fd305720a7651ea758005f03556926181 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Fri, 1 Jul 2022 18:52:28 +0100 Subject: [PATCH 08/13] Add persistent blockchain --- .gitignore | 3 + blockchain/block.go | 26 +++++++ blockchain/blockchain.go | 90 +++++++++++++++++++++---- go.mod | 22 ++++++ go.sum | 138 ++++++++++++++++++++++++++++++++++++++ main.go | 16 +++-- test/block_errors_test.go | 2 + 7 files changed, 278 insertions(+), 19 deletions(-) create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 824fac2..86e2c94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Logs logs.txt +# Temporary files +tmp/* + # Binaries for programs and plugins *.exe *.exe~ diff --git a/blockchain/block.go b/blockchain/block.go index 0d8fef4..608f515 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -1,7 +1,9 @@ package blockchain import ( + "bytes" "crypto/sha256" + "encoding/gob" "encoding/json" "strconv" "strings" @@ -63,3 +65,27 @@ func first(n []byte, _ error) []byte { func transactionToString(transaction []Transaction) string { return string(first(json.Marshal(transaction))) } + +func (bc *Block) Serialize() []byte { + var res bytes.Buffer + + encoder := gob.NewEncoder(&res) + + err := encoder.Encode(bc) + + HandleError(err) + + return res.Bytes() +} + +func Deserialize(data []byte) *Block { + var block Block + + decoder := gob.NewDecoder(bytes.NewReader(data)) + + err := decoder.Decode(&block) + + HandleError(err) + + return &block +} diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 4885d15..122aa9b 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -1,37 +1,97 @@ package blockchain import ( - "bytes" - "time" + "github.com/dgraph-io/badger" - bcErrors "github.com/MVRetailManager/MVInventoryChain/customErrors" logging "github.com/MVRetailManager/MVInventoryChain/logging" ) +const ( + dbPath = "./tmp/blockchain" +) + type Blockchain struct { - GenesisBlock Block - Blocks []Block + LastHash []byte + Database *badger.DB } func (bc *Blockchain) NewBlockchain(genesisBlock Block) { - bc.GenesisBlock = genesisBlock - bc.Blocks = []Block{genesisBlock} + opts := badger.Options{} + opts = badger.DefaultOptions(dbPath) + opts.Dir = dbPath + opts.ValueDir = dbPath + + db, err := badger.Open(opts) + HandleError(err) + + err = db.Update(func(txn *badger.Txn) error { + if _, err := txn.Get([]byte("lh")); err == badger.ErrKeyNotFound { + err := txn.Set(genesisBlock.Hash, genesisBlock.Serialize()) + HandleError(err) + err = txn.Set([]byte("lh"), genesisBlock.Hash) + + if err != nil { + logging.ErrorLogger.Printf(err.Error()) + return err + } + + bc.LastHash = genesisBlock.Hash + + return err + } else { + item, err := txn.Get([]byte("lh")) + HandleError(err) + bc.LastHash, err = item.ValueCopy(bc.LastHash) + return err + } + }) + + HandleError(err) - logging.InfoLogger.Printf("New blockchain created with genesis block: %s", bc.GenesisBlock.Hash) + bc.Database = db + + logging.InfoLogger.Printf("New blockchain created with genesis block: %s", genesisBlock.Hash) } func (bc *Blockchain) AddBlock(block Block) error { - if err := bc.isValidBlock(block); err != nil { + /*if err := bc.isValidBlock(block); err != nil { logging.WarningLogger.Printf(err.Error()) return err - } + }*/ - bc.Blocks = append(bc.Blocks, block) + err := bc.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte("lh")) + HandleError(err) + bc.LastHash, err = item.ValueCopy(bc.LastHash) + + HandleError(err) + return err + }) + + HandleError(err) + + err = bc.Database.Update(func(txn *badger.Txn) error { + err = txn.Set(block.Hash, block.Serialize()) + HandleError(err) + + err = txn.Set([]byte("lh"), block.Hash) + + if err != nil { + logging.ErrorLogger.Printf(err.Error()) + return err + } + + bc.LastHash = block.Hash + + return err + }) + + HandleError(err) return nil } -func (bc *Blockchain) isValidBlock(block Block) error { +/*func (bc *Blockchain) isValidBlock(block Block) error { if bc.isInvalidIndex(block) { return bcErrors.MismatchedIndex{ExpectedIndex: len(bc.Blocks), ActualIndex: block.Index}.DoError() } @@ -96,4 +156,10 @@ func (bc *Blockchain) isInputValueLessZero(transaction Transaction) error { } return nil +}*/ + +func HandleError(err error) { + if err != nil { + logging.ErrorLogger.Printf(err.Error()) + } } diff --git a/go.mod b/go.mod index 3d51aec..65258e9 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,25 @@ module github.com/MVRetailManager/MVInventoryChain go 1.18 + +require github.com/dgraph-io/badger v1.6.2 + +require ( + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/dgraph-io/badger/v3 v3.2103.2 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect + github.com/golang/protobuf v1.3.1 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v1.12.1 // indirect + github.com/klauspost/compress v1.12.3 // indirect + github.com/pkg/errors v0.9.1 // indirect + go.opencensus.io v0.22.5 // indirect + golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..48c107c --- /dev/null +++ b/go.sum @@ -0,0 +1,138 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8= +github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index ac96aa8..06e6b26 100644 --- a/main.go +++ b/main.go @@ -7,10 +7,6 @@ import ( logging "github.com/MVRetailManager/MVInventoryChain/logging" ) -var ( - bc blockchainPKG.Blockchain -) - func init() { logging.SetupLogger() @@ -18,7 +14,7 @@ func init() { } func main() { - genesisBlock := initGenesis() + /*genesisBlock := initGenesis() genesisBlock.Mine() bc.NewBlockchain(genesisBlock) @@ -29,7 +25,13 @@ func main() { 1, []blockchainPKG.Transaction{ { - Inputs: []blockchainPKG.Output{bc.Blocks[0].Transaction[0].Outputs[1]}, + Inputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Larry", + Value: 200, + }, + }, Outputs: []blockchainPKG.Output{ { Index: 0, @@ -44,7 +46,7 @@ func main() { block.Mine() if bc.AddBlock(*block) != nil { logging.ErrorLogger.Println("Error adding block") - } + }*/ } func initGenesis() blockchainPKG.Block { diff --git a/test/block_errors_test.go b/test/block_errors_test.go index a78dc10..bf80ec7 100644 --- a/test/block_errors_test.go +++ b/test/block_errors_test.go @@ -1,5 +1,6 @@ package test +/* import ( "testing" "time" @@ -596,3 +597,4 @@ func TestNewBlockInvalidInput(t *testing.T) { t.Errorf("Expected false, got true") } } +*/ From 02a96856769c327fc44385ceb5143a025e66763a Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Fri, 1 Jul 2022 19:50:06 +0100 Subject: [PATCH 09/13] Add blockchain iterator --- blockchain/blockchain.go | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 122aa9b..de93f0d 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -15,6 +15,11 @@ type Blockchain struct { Database *badger.DB } +type BlockchainIterator struct { + CurrentHash []byte + Database *badger.DB +} + func (bc *Blockchain) NewBlockchain(genesisBlock Block) { opts := badger.Options{} opts = badger.DefaultOptions(dbPath) @@ -41,7 +46,7 @@ func (bc *Blockchain) NewBlockchain(genesisBlock Block) { } else { item, err := txn.Get([]byte("lh")) HandleError(err) - bc.LastHash, err = item.ValueCopy(bc.LastHash) + bc.LastHash, err = item.ValueCopy(nil) return err } }) @@ -62,7 +67,7 @@ func (bc *Blockchain) AddBlock(block Block) error { err := bc.Database.View(func(txn *badger.Txn) error { item, err := txn.Get([]byte("lh")) HandleError(err) - bc.LastHash, err = item.ValueCopy(bc.LastHash) + bc.LastHash, err = item.ValueCopy(nil) HandleError(err) return err @@ -91,6 +96,31 @@ func (bc *Blockchain) AddBlock(block Block) error { return nil } +func (bc *Blockchain) Iterator() *BlockchainIterator { + iter := &BlockchainIterator{bc.LastHash, bc.Database} + + return iter +} + +func (iter *BlockchainIterator) Next() *Block { + var block *Block + + err := iter.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get(iter.CurrentHash) + HandleError(err) + + encodedBlock, err := item.ValueCopy(nil) + block = Deserialize(encodedBlock) + + return err + }) + HandleError(err) + + iter.CurrentHash = block.PreviousHash + + return block +} + /*func (bc *Blockchain) isValidBlock(block Block) error { if bc.isInvalidIndex(block) { return bcErrors.MismatchedIndex{ExpectedIndex: len(bc.Blocks), ActualIndex: block.Index}.DoError() From 3563f0a7aba0a3aae00d16b118761a27ce05fc63 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Mon, 4 Jul 2022 19:59:28 +0100 Subject: [PATCH 10/13] Refactor, persistence database and CLI --- .gitignore | 3 + blockchain/block.go | 10 +- blockchain/blockchain.go | 295 ++++++++++++++++++++++------------- blockchain/transaction.go | 103 ++++++++++-- cli/cli.go | 160 +++++++++++++++++++ customErrors/customErrors.go | 2 + main.go | 88 ++++------- 7 files changed, 478 insertions(+), 183 deletions(-) create mode 100644 cli/cli.go diff --git a/.gitignore b/.gitignore index 86e2c94..e1f8056 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ logs.txt # Temporary files tmp/* +# VSCode +.vscode/* + # Binaries for programs and plugins *.exe *.exe~ diff --git a/blockchain/block.go b/blockchain/block.go index 608f515..9b6dbb2 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -18,17 +18,17 @@ type Block struct { PreviousHash []byte Nonce int Difficulty int - Transaction []Transaction + Transaction []*Transaction } -func NewBlock(index int, unixTimeStamp int64, previousHash []byte, difficulty int, transaction []Transaction) *Block { +func NewBlock(index int, unixTimeStamp int64, previousHash []byte, transaction []*Transaction) *Block { b := &Block{ Index: index, UnixTimeStamp: unixTimeStamp, Hash: make([]byte, 32), PreviousHash: previousHash, Nonce: 0, - Difficulty: difficulty, + Difficulty: 1, Transaction: transaction, } @@ -45,7 +45,7 @@ func (b *Block) Mine() { b.Hash = b.calculateHash() } - logging.BlocksLogger.Printf("Block Mined: %v\n", b) + logging.BlocksLogger.Printf("Block Mined:\n Nonce: %v\nHash: %v\n", b.Nonce, b.Hash) } func (b *Block) calculateHash() []byte { @@ -62,7 +62,7 @@ func first(n []byte, _ error) []byte { return n } -func transactionToString(transaction []Transaction) string { +func transactionToString(transaction []*Transaction) string { return string(first(json.Marshal(transaction))) } diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index de93f0d..8cdf83f 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -1,13 +1,21 @@ package blockchain import ( + "encoding/hex" + "fmt" + "os" + "runtime" + "time" + "github.com/dgraph-io/badger" logging "github.com/MVRetailManager/MVInventoryChain/logging" ) const ( - dbPath = "./tmp/blockchain" + dbPath = "./tmp/blockchain" + dbFile = "./tmp/blockchain/MANIFEST" + genesisData = "Genesis Block" ) type Blockchain struct { @@ -20,173 +28,244 @@ type BlockchainIterator struct { Database *badger.DB } -func (bc *Blockchain) NewBlockchain(genesisBlock Block) { - opts := badger.Options{} - opts = badger.DefaultOptions(dbPath) - opts.Dir = dbPath - opts.ValueDir = dbPath +func (bc *Blockchain) InitBlockchain(address string) { + var genesis Block + + if DBexists() { + fmt.Println("Blockchain already exists") + runtime.Goexit() + } + + opts := InitDBOpts() db, err := badger.Open(opts) HandleError(err) err = db.Update(func(txn *badger.Txn) error { - if _, err := txn.Get([]byte("lh")); err == badger.ErrKeyNotFound { - err := txn.Set(genesisBlock.Hash, genesisBlock.Serialize()) - HandleError(err) - err = txn.Set([]byte("lh"), genesisBlock.Hash) - - if err != nil { - logging.ErrorLogger.Printf(err.Error()) - return err - } + cbtx := CoinbaseTx(address, genesisData) + genesis = initGenesis([]*Transaction{cbtx}) + err = txn.Set(genesis.Hash, genesis.Serialize()) + HandleError(err) + err = txn.Set([]byte("lh"), genesis.Hash) - bc.LastHash = genesisBlock.Hash + bc.LastHash = genesis.Hash - return err - } else { - item, err := txn.Get([]byte("lh")) - HandleError(err) - bc.LastHash, err = item.ValueCopy(nil) - return err - } + return err }) HandleError(err) bc.Database = db - logging.InfoLogger.Printf("New blockchain created with genesis block: %s", genesisBlock.Hash) + logging.InfoLogger.Printf("New blockchain created with genesis block: %s", genesis.Hash) } -func (bc *Blockchain) AddBlock(block Block) error { - /*if err := bc.isValidBlock(block); err != nil { - logging.WarningLogger.Printf(err.Error()) - return err - }*/ +func DBexists() bool { + _, err := os.Stat(dbFile) - err := bc.Database.View(func(txn *badger.Txn) error { + return err == nil +} + +func (bc *Blockchain) ContinueBlockchain(address string) { + if !DBexists() { + fmt.Println("No existing blockchain found, please create one.") + runtime.Goexit() + } + + opts := InitDBOpts() + + db, err := badger.Open(opts) + HandleError(err) + + err = db.Update(func(txn *badger.Txn) error { item, err := txn.Get([]byte("lh")) HandleError(err) - bc.LastHash, err = item.ValueCopy(nil) - HandleError(err) + err = item.Value(func(val []byte) error { + bc.LastHash = val + return nil + }) + return err }) - HandleError(err) - err = bc.Database.Update(func(txn *badger.Txn) error { - err = txn.Set(block.Hash, block.Serialize()) + bc.Database = db +} + +func (bc *Blockchain) FindUnspentTxs(address string) []Transaction { + var unspentTxs []Transaction + + spentTXOs := make(map[string][]int) + + iter := bc.Iterator() + + for { + block, err := iter.Next() HandleError(err) - err = txn.Set([]byte("lh"), block.Hash) + for _, tx := range block.Transaction { + txID := hex.EncodeToString(tx.ID) + + Outputs: + for outIdx, out := range tx.Outputs { + if spentTXOs[txID] != nil { + for _, spentOut := range spentTXOs[txID] { + if spentOut == outIdx { + continue Outputs + } + } + } + if out.CanBeUnlocked(address) { + unspentTxs = append(unspentTxs, *tx) + } + } + if !tx.IsCoinbase() { + for _, in := range tx.Inputs { + if in.CanUnlock(address) { + inTxID := hex.EncodeToString(in.ID) + spentTXOs[inTxID] = append(spentTXOs[inTxID], in.OutputIndex) + } + } + } + } - if err != nil { - logging.ErrorLogger.Printf(err.Error()) - return err + if len(block.PreviousHash) == 0 { + break } + } - bc.LastHash = block.Hash + return unspentTxs +} - return err - }) +func (bc *Blockchain) HandleUnspentTxs(address string) []TxOutput { + var unspentTxs []TxOutput - HandleError(err) + unspentTransactions := bc.FindUnspentTxs(address) + + for _, tx := range unspentTransactions { + for _, out := range tx.Outputs { + if out.CanBeUnlocked(address) { + unspentTxs = append(unspentTxs, out) + } + } + } - return nil + return unspentTxs } -func (bc *Blockchain) Iterator() *BlockchainIterator { - iter := &BlockchainIterator{bc.LastHash, bc.Database} +func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { + unspentOuts := make(map[string][]int) + unspentTxs := bc.FindUnspentTxs(address) + acc := 0 - return iter +Work: + for _, tx := range unspentTxs { + txID := hex.EncodeToString(tx.ID) + + for outIdx, out := range tx.Outputs { + if out.CanBeUnlocked(address) && acc < amount { + acc = out.Value + unspentOuts[txID] = append(unspentOuts[txID], outIdx) + + if acc >= amount { + break Work + } + } + } + } + + return acc, unspentOuts } -func (iter *BlockchainIterator) Next() *Block { - var block *Block +func InitDBOpts() badger.Options { + opts := badger.Options{} + opts = badger.DefaultOptions(dbPath) + opts.Dir = dbPath + opts.ValueDir = dbPath + opts.Logger = nil - err := iter.Database.View(func(txn *badger.Txn) error { - item, err := txn.Get(iter.CurrentHash) - HandleError(err) + return opts +} - encodedBlock, err := item.ValueCopy(nil) - block = Deserialize(encodedBlock) +func (bc *Blockchain) AddBlock(block Block) { + err := bc.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte("lh")) + HandleError(err) + err = item.Value(func(val []byte) error { + bc.LastHash = val + return nil + }) return err }) HandleError(err) - iter.CurrentHash = block.PreviousHash + nbIndex, _ := bc.Database.Size() + newBlock := NewBlock(int(nbIndex), time.Now().UnixNano(), bc.LastHash, block.Transaction) - return block -} + err = bc.Database.Update(func(txn *badger.Txn) error { + err := txn.Set(newBlock.Hash, newBlock.Serialize()) + HandleError(err) + err = txn.Set([]byte("lh"), newBlock.Hash) -/*func (bc *Blockchain) isValidBlock(block Block) error { - if bc.isInvalidIndex(block) { - return bcErrors.MismatchedIndex{ExpectedIndex: len(bc.Blocks), ActualIndex: block.Index}.DoError() - } - if bc.isAchronTimestamp(block) { - return bcErrors.AchronologicalTimestamp{ExpectedTimestamp: bc.Blocks[len(bc.Blocks)-1].UnixTimeStamp, ActualTimestamp: block.UnixTimeStamp}.DoError() - } - if bc.isInvalidTimestamp(block) { - return bcErrors.InvalidTimestamp{ExpectedTimestamp: time.Now().UTC().UnixNano(), ActualTimestamp: block.UnixTimeStamp}.DoError() - } - if bc.isInvalidPreviousHash(block) { - return bcErrors.InvalidPreviousHash{ExpectedHash: bc.Blocks[len(bc.Blocks)-1].Hash, ActualHash: block.PreviousHash}.DoError() - } - if bc.isInvalidGenesisBlock(block) { - return bcErrors.InvalidGenesisBlock{ExpectedFormat: make([]byte, 32), ActualFormat: bc.GenesisBlock.Hash}.DoError() - } + bc.LastHash = newBlock.Hash - for _, transaction := range block.Transaction { - if err := bc.isInsufficientInputValue(transaction); err != nil { - return err - } - if err := bc.isInputValueLessZero(transaction); err != nil { - return err - } - } + return err + }) - return nil + HandleError(err) } -func (bc *Blockchain) isInvalidIndex(block Block) bool { - return block.Index != len(bc.Blocks) -} +func (bc *Blockchain) Iterator() *BlockchainIterator { + iter := &BlockchainIterator{bc.LastHash, bc.Database} -func (bc *Blockchain) isAchronTimestamp(block Block) bool { - return block.UnixTimeStamp < bc.Blocks[len(bc.Blocks)-1].UnixTimeStamp + return iter } -func (bc *Blockchain) isInvalidTimestamp(block Block) bool { - return block.UnixTimeStamp > time.Now().UTC().UnixNano() -} +func (iter *BlockchainIterator) Next() (*Block, error) { + var block *Block + var encodedBlock []byte -func (bc *Blockchain) isInvalidPreviousHash(block Block) bool { - return !bytes.Equal(block.PreviousHash, bc.Blocks[len(bc.Blocks)-1].Hash) -} + err := iter.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get(iter.CurrentHash) -func (bc *Blockchain) isInvalidGenesisBlock(block Block) bool { - return !bytes.Equal(bc.GenesisBlock.Hash, make([]byte, 32)) -} + if err != nil { + HandleError(err) + return err + } + + err = item.Value(func(val []byte) error { + encodedBlock = val + return nil + }) + + block = Deserialize(encodedBlock) + + return err + }) -func (bc *Blockchain) isInsufficientInputValue(transaction Transaction) error { - if transaction.inputValue() < transaction.outputValue() { - return bcErrors.InsufficientInputValue{ExpectedValue: transaction.inputValue(), ActualValue: transaction.outputValue()}.DoError() + if err != nil { + HandleError(err) + return nil, err } - return nil + iter.CurrentHash = block.PreviousHash + + return block, nil } -func (bc *Blockchain) isInputValueLessZero(transaction Transaction) error { - for _, input := range transaction.Inputs { - if input.Value < 0 { - return bcErrors.InvalidInput{ExpectedValue: 0, ActualValue: input.Value}.DoError() - } +func initGenesis(coinbase []*Transaction) Block { + return Block{ + Index: 0, + UnixTimeStamp: time.Now().UTC().UnixNano(), + Hash: make([]byte, 32), + PreviousHash: nil, + Nonce: 0, + Difficulty: 0, + Transaction: coinbase, } - - return nil -}*/ +} func HandleError(err error) { if err != nil { diff --git a/blockchain/transaction.go b/blockchain/transaction.go index aa7b3b1..f832799 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -1,26 +1,105 @@ package blockchain +import ( + "bytes" + "crypto/sha256" + "encoding/gob" + "encoding/hex" + "fmt" + "runtime" + + "github.com/MVRetailManager/MVInventoryChain/logging" +) + +const ( + reward = 100 +) + type Transaction struct { - Inputs []Output - Outputs []Output + ID []byte + Inputs []TxInput + Outputs []TxOutput +} + +type TxOutput struct { + Value int + PubKey string +} + +type TxInput struct { + ID []byte + OutputIndex int + Sig string } -func (t *Transaction) inputValue() int { - var total int +func (tx *Transaction) SetID() { + var encoded bytes.Buffer + var hash [32]byte - for _, input := range t.Inputs { - total += input.Value + encode := gob.NewEncoder(&encoded) + err := encode.Encode(tx) + HandleError(err) + + hash = sha256.Sum256(encoded.Bytes()) + tx.ID = hash[:] +} + +func CoinbaseTx(to, data string) *Transaction { + if data == "" { + data = fmt.Sprintf("Reward to '%s'", to) } - return total + twin := TxInput{[]byte{}, -1, data} + txout := TxOutput{reward, to} + + tx := Transaction{nil, []TxInput{twin}, []TxOutput{txout}} + tx.SetID() + + return &tx } -func (t *Transaction) outputValue() int { - var total int +func NewTransaction(from, to string, amount int, bc *Blockchain) *Transaction { + var inputs []TxInput + var outputs []TxOutput + + acc, validOutputs := bc.FindSpendableOutputs(from, amount) - for _, output := range t.Outputs { - total += output.Value + if acc < amount { + fmt.Printf("Not enough funds on address %s\n", from) + logging.WarningLogger.Printf("Not enough funds on address %s\n", from) + runtime.Goexit() } - return total + for txid, outs := range validOutputs { + txID, err := hex.DecodeString(txid) + HandleError(err) + + for _, out := range outs { + input := TxInput{txID, out, from} + inputs = append(inputs, input) + } + } + + outputs = append(outputs, TxOutput{amount, to}) + + if acc > amount { + outputs = append(outputs, TxOutput{acc - amount, from}) + } + + tx := Transaction{nil, inputs, outputs} + tx.SetID() + + return &tx +} + +func (tx *Transaction) IsCoinbase() bool { + return len(tx.Inputs) == 1 && len(tx.Inputs[0].ID) == 0 && tx.Inputs[0].OutputIndex == -1 +} + +func (in *TxInput) CanUnlock(data string) bool { + return in.Sig == data +} + +func (out *TxOutput) CanBeUnlocked(address string) bool { + return out.PubKey == address } diff --git a/cli/cli.go b/cli/cli.go new file mode 100644 index 0000000..01b1042 --- /dev/null +++ b/cli/cli.go @@ -0,0 +1,160 @@ +package cli + +import ( + "flag" + "fmt" + "os" + "runtime" + "time" + + blockchainPKG "github.com/MVRetailManager/MVInventoryChain/blockchain" + "github.com/MVRetailManager/MVInventoryChain/logging" +) + +type CLI struct{} + +var bc blockchainPKG.Blockchain + +func (cli *CLI) printUsage() { + logging.InfoLogger.Println("Usage command executed.") + + fmt.Println("Usage:") + fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS") + fmt.Println(" createblockchain -address ADDRESS - Create a new blockchain and save it to disk, ADDRESS will be the address of the coinbase transaction") + fmt.Println(" printchain - Print the blockchain") + fmt.Println(" send -from FROM -to TO- amount AMOUNT - Send AMOUNT of coins from FROM to TO") +} + +func (cli *CLI) validateArgs() { + if len(os.Args) < 2 { + cli.printUsage() + runtime.Goexit() + } +} + +func (cli *CLI) printChain() { + bc.ContinueBlockchain("") + defer bc.Database.Close() + + iter := bc.Iterator() + + for { + block, err := iter.Next() + blockchainPKG.HandleError(err) + + fmt.Printf("\nS==========%d==========S\n", block.Index) + fmt.Printf("Previous Hash: %x\n", block.PreviousHash) + fmt.Printf("Hash: %x\n", block.Hash) + fmt.Printf("Nonce: %d\n", block.Nonce) + fmt.Printf("Difficulty: %d\n", block.Difficulty) + fmt.Printf("TimeStamp: %d\n", block.UnixTimeStamp) + fmt.Printf("Transactions: %d\n", len(block.Transaction)) + fmt.Printf("E==========%d==========E\n", block.Index) + } +} + +func (cli *CLI) createBlockchain(address string) { + bc.InitBlockchain(address) + bc.Database.Close() + fmt.Println("Success!") +} + +func (cli *CLI) getBalance(address string) { + bc.ContinueBlockchain(address) + defer bc.Database.Close() + + balance := 0 + UTXOs := bc.HandleUnspentTxs(address) + + for _, out := range UTXOs { + balance += out.Value + } + + fmt.Printf("Balance of '%s': %d\n", address, balance) +} + +func (cli *CLI) send(from, to string, amount int) { + bc.ContinueBlockchain(from) + defer bc.Database.Close() + + tx := blockchainPKG.NewTransaction(from, to, amount, &bc) + + nbIndex, _ := bc.Database.Size() + bc.AddBlock(*blockchainPKG.NewBlock(int(nbIndex), time.Now().UTC().UnixNano(), bc.LastHash, []*blockchainPKG.Transaction{tx})) + + fmt.Print("Success!") +} + +func (cli *CLI) Run() { + bc = blockchainPKG.Blockchain{} + + cli.validateArgs() + + getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) + createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) + printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) + sendCmd := flag.NewFlagSet("send", flag.ExitOnError) + + getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for") + createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to") + sendFrom := sendCmd.String("from", "", "Source wallet address") + sendTo := sendCmd.String("to", "", "Destination wallet address") + sendAmount := sendCmd.Int("amount", 0, "Amount to send") + + switch os.Args[1] { + case "getbalance": + err := getBalanceCmd.Parse(os.Args[2:]) + if err != nil { + logging.ErrorLogger.Panic(err) + } + case "createblockchain": + err := createBlockchainCmd.Parse(os.Args[2:]) + if err != nil { + logging.ErrorLogger.Panic(err) + } + case "printchain": + err := printChainCmd.Parse(os.Args[2:]) + if err != nil { + logging.ErrorLogger.Panic(err) + } + case "send": + err := sendCmd.Parse(os.Args[2:]) + if err != nil { + logging.ErrorLogger.Panic(err) + } + default: + cli.printUsage() + runtime.Goexit() + } + + if getBalanceCmd.Parsed() { + if *getBalanceAddress == "" { + getBalanceCmd.Usage() + runtime.Goexit() + } + cli.getBalance(*getBalanceAddress) + } + + if createBlockchainCmd.Parsed() { + if *createBlockchainAddress == "" { + createBlockchainCmd.Usage() + runtime.Goexit() + } + cli.createBlockchain(*createBlockchainAddress) + } + + if printChainCmd.Parsed() { + cli.printChain() + } + + if sendCmd.Parsed() { + if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 { + sendCmd.Usage() + runtime.Goexit() + } + + cli.send(*sendFrom, *sendTo, *sendAmount) + } + + runtime.Goexit() +} diff --git a/customErrors/customErrors.go b/customErrors/customErrors.go index d3f7de6..f74448e 100644 --- a/customErrors/customErrors.go +++ b/customErrors/customErrors.go @@ -1,5 +1,6 @@ package customErrors +/* import ( "errors" "fmt" @@ -158,3 +159,4 @@ func (ii InvalidInput) DoError() error { err: errors.New("InvalidInputError"), } } +*/ diff --git a/main.go b/main.go index 06e6b26..d4e895e 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,9 @@ package main import ( - "time" + "os" - blockchainPKG "github.com/MVRetailManager/MVInventoryChain/blockchain" + "github.com/MVRetailManager/MVInventoryChain/cli" logging "github.com/MVRetailManager/MVInventoryChain/logging" ) @@ -14,65 +14,37 @@ func init() { } func main() { - /*genesisBlock := initGenesis() - genesisBlock.Mine() - bc.NewBlockchain(genesisBlock) - - block := blockchainPKG.NewBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.Hash, - 1, - []blockchainPKG.Transaction{ - { - Inputs: []blockchainPKG.Output{ - { - Index: 0, - Address: "Larry", - Value: 200, + defer os.Exit(0) + cmdline := cli.CLI{} + cmdline.Run() + /* + block := blockchainPKG.NewBlock( + 1, + time.Now().UTC().UnixNano(), + genesisBlock.Hash, + 1, + []blockchainPKG.Transaction{ + { + Inputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Larry", + Value: 200, + }, }, - }, - Outputs: []blockchainPKG.Output{ - { - Index: 0, - Address: "Alice", - Value: 2, + Outputs: []blockchainPKG.Output{ + { + Index: 0, + Address: "Alice", + Value: 2, + }, }, }, }, - }, - ) - - block.Mine() - if bc.AddBlock(*block) != nil { - logging.ErrorLogger.Println("Error adding block") - }*/ -} + ) -func initGenesis() blockchainPKG.Block { - return blockchainPKG.Block{ - Index: 0, - UnixTimeStamp: time.Now().UTC().UnixNano(), - Hash: make([]byte, 32), - PreviousHash: nil, - Nonce: 0, - Difficulty: 0, - Transaction: []blockchainPKG.Transaction{ - { - Inputs: make([]blockchainPKG.Output, 0), - Outputs: []blockchainPKG.Output{ - { - Index: 0, - Address: "Alice", - Value: 30, - }, - { - Index: 1, - Address: "Bob", - Value: 7, - }, - }, - }, - }, - } + block.Mine() + if bc.AddBlock(*block) != nil { + logging.ErrorLogger.Println("Error adding block") + }*/ } From 731634b598663d2b1cbb8626f162078f2563b84f Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Mon, 4 Jul 2022 20:03:03 +0100 Subject: [PATCH 11/13] Fix ineffassign --- blockchain/blockchain.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 8cdf83f..319909d 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -179,8 +179,7 @@ Work: } func InitDBOpts() badger.Options { - opts := badger.Options{} - opts = badger.DefaultOptions(dbPath) + opts := badger.DefaultOptions(dbPath) opts.Dir = dbPath opts.ValueDir = dbPath opts.Logger = nil From 44a2eab7462f880cf2e2a868e6fad9b57a3f8243 Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Mon, 4 Jul 2022 20:42:17 +0100 Subject: [PATCH 12/13] Add wallets --- blockchain/transaction.go | 19 -------- blockchain/tx.go | 20 ++++++++ cli/cli.go | 40 ++++++++++++++++ go.mod | 6 ++- go.sum | 8 ++++ main.go | 32 +------------ wallet/utils.go | 13 ++++++ wallet/wallet.go | 79 +++++++++++++++++++++++++++++++ wallet/wallets.go | 98 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 263 insertions(+), 52 deletions(-) create mode 100644 blockchain/tx.go create mode 100644 wallet/utils.go create mode 100644 wallet/wallet.go create mode 100644 wallet/wallets.go diff --git a/blockchain/transaction.go b/blockchain/transaction.go index f832799..0dc6f0d 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -21,17 +21,6 @@ type Transaction struct { Outputs []TxOutput } -type TxOutput struct { - Value int - PubKey string -} - -type TxInput struct { - ID []byte - OutputIndex int - Sig string -} - func (tx *Transaction) SetID() { var encoded bytes.Buffer var hash [32]byte @@ -95,11 +84,3 @@ func NewTransaction(from, to string, amount int, bc *Blockchain) *Transaction { func (tx *Transaction) IsCoinbase() bool { return len(tx.Inputs) == 1 && len(tx.Inputs[0].ID) == 0 && tx.Inputs[0].OutputIndex == -1 } - -func (in *TxInput) CanUnlock(data string) bool { - return in.Sig == data -} - -func (out *TxOutput) CanBeUnlocked(address string) bool { - return out.PubKey == address -} diff --git a/blockchain/tx.go b/blockchain/tx.go new file mode 100644 index 0000000..a31a0d1 --- /dev/null +++ b/blockchain/tx.go @@ -0,0 +1,20 @@ +package blockchain + +type TxOutput struct { + Value int + PubKey string +} + +type TxInput struct { + ID []byte + OutputIndex int + Sig string +} + +func (in *TxInput) CanUnlock(data string) bool { + return in.Sig == data +} + +func (out *TxOutput) CanBeUnlocked(address string) bool { + return out.PubKey == address +} diff --git a/cli/cli.go b/cli/cli.go index 01b1042..849c960 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -9,6 +9,7 @@ import ( blockchainPKG "github.com/MVRetailManager/MVInventoryChain/blockchain" "github.com/MVRetailManager/MVInventoryChain/logging" + "github.com/MVRetailManager/MVInventoryChain/wallet" ) type CLI struct{} @@ -23,6 +24,8 @@ func (cli *CLI) printUsage() { fmt.Println(" createblockchain -address ADDRESS - Create a new blockchain and save it to disk, ADDRESS will be the address of the coinbase transaction") fmt.Println(" printchain - Print the blockchain") fmt.Println(" send -from FROM -to TO- amount AMOUNT - Send AMOUNT of coins from FROM to TO") + fmt.Println(" createwallet - Create a new wallet") + fmt.Println(" listaddresses - List all addresses stored within wallet file") } func (cli *CLI) validateArgs() { @@ -85,6 +88,23 @@ func (cli *CLI) send(from, to string, amount int) { fmt.Print("Success!") } +func (cli *CLI) createWallet() { + wallets, _ := wallet.CreateWallets() + address := wallets.AddWallet() + wallets.SaveFile() + + fmt.Printf("New address: %s\n", address) +} + +func (cli *CLI) listAddresses() { + wallets, _ := wallet.CreateWallets() + addresses := wallets.GetAllAddresses() + + for _, address := range addresses { + fmt.Println(address) + } +} + func (cli *CLI) Run() { bc = blockchainPKG.Blockchain{} @@ -94,6 +114,8 @@ func (cli *CLI) Run() { createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) sendCmd := flag.NewFlagSet("send", flag.ExitOnError) + createWalletsCmd := flag.NewFlagSet("createwallet", flag.ExitOnError) + listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError) getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for") createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to") @@ -122,6 +144,16 @@ func (cli *CLI) Run() { if err != nil { logging.ErrorLogger.Panic(err) } + case "createwallet": + err := createWalletsCmd.Parse(os.Args[2:]) + if err != nil { + logging.ErrorLogger.Panic(err) + } + case "listaddresses": + err := listAddressesCmd.Parse(os.Args[2:]) + if err != nil { + logging.ErrorLogger.Panic(err) + } default: cli.printUsage() runtime.Goexit() @@ -156,5 +188,13 @@ func (cli *CLI) Run() { cli.send(*sendFrom, *sendTo, *sendAmount) } + if createWalletsCmd.Parsed() { + cli.createWallet() + } + + if listAddressesCmd.Parsed() { + cli.listAddresses() + } + runtime.Goexit() } diff --git a/go.mod b/go.mod index 65258e9..f7fa060 100644 --- a/go.mod +++ b/go.mod @@ -18,8 +18,10 @@ require ( github.com/golang/snappy v0.0.3 // indirect github.com/google/flatbuffers v1.12.1 // indirect github.com/klauspost/compress v1.12.3 // indirect + github.com/mr-tron/base58 v1.2.0 github.com/pkg/errors v0.9.1 // indirect go.opencensus.io v0.22.5 // indirect - golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect ) diff --git a/go.sum b/go.sum index 48c107c..a171d3a 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -82,6 +84,8 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -97,6 +101,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -113,6 +119,8 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/main.go b/main.go index d4e895e..4033968 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "os" "github.com/MVRetailManager/MVInventoryChain/cli" - logging "github.com/MVRetailManager/MVInventoryChain/logging" + "github.com/MVRetailManager/MVInventoryChain/logging" ) func init() { @@ -17,34 +17,4 @@ func main() { defer os.Exit(0) cmdline := cli.CLI{} cmdline.Run() - /* - block := blockchainPKG.NewBlock( - 1, - time.Now().UTC().UnixNano(), - genesisBlock.Hash, - 1, - []blockchainPKG.Transaction{ - { - Inputs: []blockchainPKG.Output{ - { - Index: 0, - Address: "Larry", - Value: 200, - }, - }, - Outputs: []blockchainPKG.Output{ - { - Index: 0, - Address: "Alice", - Value: 2, - }, - }, - }, - }, - ) - - block.Mine() - if bc.AddBlock(*block) != nil { - logging.ErrorLogger.Println("Error adding block") - }*/ } diff --git a/wallet/utils.go b/wallet/utils.go new file mode 100644 index 0000000..3bc8a1e --- /dev/null +++ b/wallet/utils.go @@ -0,0 +1,13 @@ +package wallet + +import ( + "github.com/mr-tron/base58" +) + +func Base58Encode(input []byte) []byte { + return []byte(base58.Encode(input)) +} + +func Base58Decode(input []byte) ([]byte, error) { + return base58.Decode(string(input[:])) +} diff --git a/wallet/wallet.go b/wallet/wallet.go new file mode 100644 index 0000000..d619761 --- /dev/null +++ b/wallet/wallet.go @@ -0,0 +1,79 @@ +package wallet + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "runtime" + + "golang.org/x/crypto/ripemd160" + + "github.com/MVRetailManager/MVInventoryChain/logging" +) + +const ( + checksumLen = 4 + version = byte(0x00) +) + +type Wallet struct { + PrivateKey ecdsa.PrivateKey + PublicKey []byte +} + +func NewKeyPair() (ecdsa.PrivateKey, []byte) { + curve := elliptic.P256() + + priKey, err := ecdsa.GenerateKey(curve, rand.Reader) + + if err != nil { + logging.ErrorLogger.Printf("%v", err) + runtime.Goexit() + } + + pubKey := append(priKey.PublicKey.X.Bytes(), priKey.PublicKey.Y.Bytes()...) + + return *priKey, pubKey +} + +func NewWallet() *Wallet { + privateKey, publicKey := NewKeyPair() + + return &Wallet{privateKey, publicKey} +} + +func PublicKeyHash(pubKey []byte) []byte { + pubHash := sha256.Sum256(pubKey) + + hasher := ripemd160.New() + _, err := hasher.Write(pubHash[:]) + if err != nil { + logging.ErrorLogger.Printf("%v", err) + runtime.Goexit() + } + + publicRipMD160Hash := hasher.Sum(nil) + + return publicRipMD160Hash +} + +func Checksum(data []byte) []byte { + fstHash := sha256.Sum256(data) + sndHash := sha256.Sum256(fstHash[:]) + + return sndHash[:checksumLen] +} + +func (w Wallet) Address() []byte { + pubHash := PublicKeyHash(w.PublicKey) + + versionHash := append([]byte{version}, pubHash...) + checksum := Checksum(versionHash) + + fullHash := append(versionHash, checksum...) + + address := Base58Encode(fullHash) + + return address +} diff --git a/wallet/wallets.go b/wallet/wallets.go new file mode 100644 index 0000000..e0ebeaa --- /dev/null +++ b/wallet/wallets.go @@ -0,0 +1,98 @@ +package wallet + +import ( + "bytes" + "crypto/elliptic" + "encoding/gob" + "fmt" + "io/ioutil" + "os" + "runtime" + + "github.com/MVRetailManager/MVInventoryChain/logging" +) + +const walletFile = "./tmp/wallets.dat" + +type Wallets struct { + Wallets map[string]*Wallet +} + +func (ws *Wallets) SaveFile() { + var content bytes.Buffer + + gob.Register(elliptic.P256()) + + encoder := gob.NewEncoder(&content) + err := encoder.Encode(ws) + if err != nil { + logging.ErrorLogger.Printf("%v", err) + runtime.Goexit() + } + + err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) + if err != nil { + logging.ErrorLogger.Printf("%v", err) + runtime.Goexit() + } +} + +func (ws *Wallets) LoadFile() error { + if _, err := os.Stat(walletFile); os.IsNotExist(err) { + return err + } + + var wallets Wallets + + fileContent, err := ioutil.ReadFile(walletFile) + if err != nil { + logging.ErrorLogger.Printf("%v", err) + runtime.Goexit() + } + + gob.Register(elliptic.P256()) + decoder := gob.NewDecoder(bytes.NewReader(fileContent)) + + err = decoder.Decode(&wallets) + if err != nil { + logging.ErrorLogger.Printf("%v", err) + runtime.Goexit() + } + + ws.Wallets = wallets.Wallets + + return nil +} + +func CreateWallets() (*Wallets, error) { + wallets := &Wallets{} + wallets.Wallets = make(map[string]*Wallet) + + err := wallets.LoadFile() + + return wallets, err +} + +func (ws *Wallets) GetWallet(address string) *Wallet { + return ws.Wallets[address] +} + +func (ws *Wallets) GetAllAddresses() []string { + var addresses []string + + for address := range ws.Wallets { + addresses = append(addresses, address) + } + + return addresses +} + +func (ws *Wallets) AddWallet() string { + wallet := NewWallet() + + address := fmt.Sprintf("%s", wallet.Address()) + + ws.Wallets[address] = wallet + + return address +} From 4a87a8e4469c3e943f8a56a5704c7881d7a2f44a Mon Sep 17 00:00:00 2001 From: Dinoosawruss Date: Mon, 4 Jul 2022 21:42:48 +0100 Subject: [PATCH 13/13] Integrate wallet package with blockchain package --- blockchain/blockchain.go | 63 +++++++++++--- blockchain/transaction.go | 173 ++++++++++++++++++++++++++++++++++++-- blockchain/tx.go | 42 +++++++-- blockchain/utils.go | 9 ++ cli/cli.go | 46 ++++++++-- wallet/wallet.go | 15 ++++ 6 files changed, 314 insertions(+), 34 deletions(-) create mode 100644 blockchain/utils.go diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 319909d..d62c5f3 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -1,6 +1,8 @@ package blockchain import ( + "bytes" + "crypto/ecdsa" "encoding/hex" "fmt" "os" @@ -93,7 +95,7 @@ func (bc *Blockchain) ContinueBlockchain(address string) { bc.Database = db } -func (bc *Blockchain) FindUnspentTxs(address string) []Transaction { +func (bc *Blockchain) FindUnspentTxs(publicKeyHash []byte) []Transaction { var unspentTxs []Transaction spentTXOs := make(map[string][]int) @@ -116,13 +118,13 @@ func (bc *Blockchain) FindUnspentTxs(address string) []Transaction { } } } - if out.CanBeUnlocked(address) { + if out.IsLockedWithkey(publicKeyHash) { unspentTxs = append(unspentTxs, *tx) } } if !tx.IsCoinbase() { for _, in := range tx.Inputs { - if in.CanUnlock(address) { + if in.UsesKey(publicKeyHash) { inTxID := hex.EncodeToString(in.ID) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.OutputIndex) } @@ -138,14 +140,14 @@ func (bc *Blockchain) FindUnspentTxs(address string) []Transaction { return unspentTxs } -func (bc *Blockchain) HandleUnspentTxs(address string) []TxOutput { +func (bc *Blockchain) HandleUnspentTxs(publicKeyHash []byte) []TxOutput { var unspentTxs []TxOutput - unspentTransactions := bc.FindUnspentTxs(address) + unspentTransactions := bc.FindUnspentTxs(publicKeyHash) for _, tx := range unspentTransactions { for _, out := range tx.Outputs { - if out.CanBeUnlocked(address) { + if out.IsLockedWithkey(publicKeyHash) { unspentTxs = append(unspentTxs, out) } } @@ -154,9 +156,9 @@ func (bc *Blockchain) HandleUnspentTxs(address string) []TxOutput { return unspentTxs } -func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { +func (bc *Blockchain) FindSpendableOutputs(publicKeyHash []byte, amount int) (int, map[string][]int) { unspentOuts := make(map[string][]int) - unspentTxs := bc.FindUnspentTxs(address) + unspentTxs := bc.FindUnspentTxs(publicKeyHash) acc := 0 Work: @@ -164,7 +166,7 @@ Work: txID := hex.EncodeToString(tx.ID) for outIdx, out := range tx.Outputs { - if out.CanBeUnlocked(address) && acc < amount { + if out.IsLockedWithkey(publicKeyHash) && acc < amount { acc = out.Value unspentOuts[txID] = append(unspentOuts[txID], outIdx) @@ -266,8 +268,45 @@ func initGenesis(coinbase []*Transaction) Block { } } -func HandleError(err error) { - if err != nil { - logging.ErrorLogger.Printf(err.Error()) +func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { + iter := bc.Iterator() + + for { + block, err := iter.Next() + HandleError(err) + + for _, tx := range block.Transaction { + if bytes.Compare(tx.ID, ID) == 0 { + return *tx, nil + } + + if len(block.PreviousHash) == 0 { + break + } + } } } + +func (bc *Blockchain) SignTransaction(tx *Transaction, privateKey ecdsa.PrivateKey) { + previousTxs := make(map[string]Transaction) + + for _, in := range tx.Inputs { + pTx, err := bc.FindTransaction(in.ID) + HandleError(err) + previousTxs[hex.EncodeToString(pTx.ID)] = pTx + } + + tx.Sign(privateKey, previousTxs) +} + +func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { + previousTxs := make(map[string]Transaction) + + for _, in := range tx.Inputs { + pTx, err := bc.FindTransaction(in.ID) + HandleError(err) + previousTxs[hex.EncodeToString(pTx.ID)] = pTx + } + + return tx.Verify(previousTxs) +} diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 0dc6f0d..86d4ceb 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -2,13 +2,19 @@ package blockchain import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "crypto/sha256" "encoding/gob" "encoding/hex" "fmt" + "math/big" "runtime" + "strings" "github.com/MVRetailManager/MVInventoryChain/logging" + "github.com/MVRetailManager/MVInventoryChain/wallet" ) const ( @@ -38,10 +44,10 @@ func CoinbaseTx(to, data string) *Transaction { data = fmt.Sprintf("Reward to '%s'", to) } - twin := TxInput{[]byte{}, -1, data} - txout := TxOutput{reward, to} + twin := TxInput{[]byte{}, -1, nil, []byte(data)} + txout := NewTxOutput(reward, to) - tx := Transaction{nil, []TxInput{twin}, []TxOutput{txout}} + tx := Transaction{nil, []TxInput{twin}, []TxOutput{*txout}} tx.SetID() return &tx @@ -51,7 +57,13 @@ func NewTransaction(from, to string, amount int, bc *Blockchain) *Transaction { var inputs []TxInput var outputs []TxOutput - acc, validOutputs := bc.FindSpendableOutputs(from, amount) + wallets, err := wallet.CreateWallets() + HandleError(err) + + w := wallets.GetWallet(from) + publicKeyHash := wallet.PublicKeyHash(w.PublicKey) + + acc, validOutputs := bc.FindSpendableOutputs(publicKeyHash, amount) if acc < amount { fmt.Printf("Not enough funds on address %s\n", from) @@ -64,19 +76,21 @@ func NewTransaction(from, to string, amount int, bc *Blockchain) *Transaction { HandleError(err) for _, out := range outs { - input := TxInput{txID, out, from} + input := TxInput{txID, out, nil, w.PublicKey} inputs = append(inputs, input) } } - outputs = append(outputs, TxOutput{amount, to}) + outputs = append(outputs, *NewTxOutput(amount, to)) if acc > amount { - outputs = append(outputs, TxOutput{acc - amount, from}) + outputs = append(outputs, *NewTxOutput(acc-amount, from)) } tx := Transaction{nil, inputs, outputs} - tx.SetID() + tx.ID = tx.Hash() + + bc.SignTransaction(&tx, w.PrivateKey) return &tx } @@ -84,3 +98,146 @@ func NewTransaction(from, to string, amount int, bc *Blockchain) *Transaction { func (tx *Transaction) IsCoinbase() bool { return len(tx.Inputs) == 1 && len(tx.Inputs[0].ID) == 0 && tx.Inputs[0].OutputIndex == -1 } + +func (tx Transaction) Serialize() []byte { + var encoded bytes.Buffer + + enc := gob.NewEncoder(&encoded) + err := enc.Encode(tx) + HandleError(err) + + return encoded.Bytes() +} + +func (tx *Transaction) Hash() []byte { + var hash [32]byte + + txCopy := *tx + txCopy.ID = []byte{} + + hash = sha256.Sum256(txCopy.Serialize()) + + return hash[:] +} + +func (tx *Transaction) Sign(privateKey ecdsa.PrivateKey, previousTxs map[string]Transaction) { + if tx.IsCoinbase() { + return + } + + for _, in := range tx.Inputs { + if previousTxs[hex.EncodeToString(in.ID)].ID == nil { + logging.ErrorLogger.Printf("Error with transaction %s, previous transaction does not exist.", hex.EncodeToString(in.ID)) + runtime.Goexit() + } + } + + txCopy := tx.TrimmedCopy() + + for inId, in := range txCopy.Inputs { + prevTx := previousTxs[hex.EncodeToString(in.ID)] + + txCopy.Inputs[inId].Signature = nil + txCopy.Inputs[inId].PublicKey = prevTx.Outputs[in.OutputIndex].PublicKeyHash + + txCopy.ID = txCopy.Hash() + + txCopy.Inputs[inId].PublicKey = nil + + r, s, err := ecdsa.Sign(rand.Reader, &privateKey, txCopy.ID) + HandleError(err) + + signature := append(r.Bytes(), s.Bytes()...) + tx.Inputs[inId].Signature = signature + } +} + +func (tx Transaction) TrimmedCopy() Transaction { + var inputs []TxInput + var outputs []TxOutput + + for _, in := range tx.Inputs { + inputs = append(inputs, TxInput{in.ID, in.OutputIndex, nil, nil}) + } + + for _, out := range tx.Outputs { + outputs = append(outputs, TxOutput{out.Value, out.PublicKeyHash}) + } + + txCopy := Transaction{tx.ID, inputs, outputs} + + return txCopy +} + +func (tx *Transaction) Verify(previousTxs map[string]Transaction) bool { + if tx.IsCoinbase() { + return true + } + + for _, in := range tx.Inputs { + if previousTxs[hex.EncodeToString(in.ID)].ID == nil { + logging.ErrorLogger.Printf("Error with transaction %s, previous transaction does not exist.", hex.EncodeToString(in.ID)) + runtime.Goexit() + } + } + + txCopy := tx.TrimmedCopy() + curve := elliptic.P256() + + for inId, in := range txCopy.Inputs { + prevTx := previousTxs[hex.EncodeToString(in.ID)] + + txCopy.Inputs[inId].Signature = nil + txCopy.Inputs[inId].PublicKey = prevTx.Outputs[in.OutputIndex].PublicKeyHash + + txCopy.ID = txCopy.Hash() + + txCopy.Inputs[inId].PublicKey = nil + + r := big.Int{} + s := big.Int{} + + sigLen := len(in.Signature) + + r.SetBytes(in.Signature[:(sigLen / 2)]) + s.SetBytes(in.Signature[(sigLen / 2):]) + + x := big.Int{} + y := big.Int{} + + keyLen := len(in.PublicKey) + + x.SetBytes(in.PublicKey[:(keyLen / 2)]) + y.SetBytes(in.PublicKey[(keyLen / 2):]) + + rawPublicKey := ecdsa.PublicKey{curve, &x, &y} + + if ecdsa.Verify(&rawPublicKey, txCopy.ID, &r, &s) == false { + return false + } + } + + return true +} + +func (tx Transaction) String() string { + var lines []string + + lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID)) + + for i, input := range tx.Inputs { + lines = append(lines, fmt.Sprintf(" Input %d", i)) + lines = append(lines, fmt.Sprintf(" TXID: %d", input.ID)) + lines = append(lines, fmt.Sprintf(" OutputIndex: %d", input.OutputIndex)) + lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) + lines = append(lines, fmt.Sprintf(" PublicKey: %x", input.PublicKey)) + } + + for i, output := range tx.Outputs { + lines = append(lines, fmt.Sprintf(" Output %d", i)) + lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) + lines = append(lines, fmt.Sprintf(" PublicKeyHash: %x", output.PublicKeyHash)) + } + + return strings.Join(lines, "\n") +} diff --git a/blockchain/tx.go b/blockchain/tx.go index a31a0d1..72a1921 100644 --- a/blockchain/tx.go +++ b/blockchain/tx.go @@ -1,20 +1,48 @@ package blockchain +import ( + "bytes" + "runtime" + + logging "github.com/MVRetailManager/MVInventoryChain/logging" + "github.com/MVRetailManager/MVInventoryChain/wallet" +) + type TxOutput struct { - Value int - PubKey string + Value int + PublicKeyHash []byte } type TxInput struct { ID []byte OutputIndex int - Sig string + Signature []byte + PublicKey []byte } -func (in *TxInput) CanUnlock(data string) bool { - return in.Sig == data +func (in *TxInput) UsesKey(publicKeyHash []byte) bool { + lockingHash := wallet.PublicKeyHash(in.PublicKey) + + return bytes.Compare(lockingHash, publicKeyHash) == 0 } -func (out *TxOutput) CanBeUnlocked(address string) bool { - return out.PubKey == address +func (out *TxOutput) Lock(address []byte) { + finalKey, err := wallet.Base58Decode(address) + if err != nil { + logging.ErrorLogger.Printf("%v", err) + runtime.Goexit() + } + + out.PublicKeyHash = finalKey[1 : len(finalKey)-4] +} + +func (out *TxOutput) IsLockedWithkey(publicKeyHash []byte) bool { + return bytes.Compare(out.PublicKeyHash, publicKeyHash) == 0 +} + +func NewTxOutput(value int, address string) *TxOutput { + txOutput := &TxOutput{value, nil} + txOutput.Lock([]byte(address)) + + return txOutput } diff --git a/blockchain/utils.go b/blockchain/utils.go new file mode 100644 index 0000000..21d6bf9 --- /dev/null +++ b/blockchain/utils.go @@ -0,0 +1,9 @@ +package blockchain + +import logging "github.com/MVRetailManager/MVInventoryChain/logging" + +func HandleError(err error) { + if err != nil { + logging.ErrorLogger.Printf(err.Error()) + } +} diff --git a/cli/cli.go b/cli/cli.go index 849c960..7e6d4bd 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -46,28 +46,49 @@ func (cli *CLI) printChain() { blockchainPKG.HandleError(err) fmt.Printf("\nS==========%d==========S\n", block.Index) - fmt.Printf("Previous Hash: %x\n", block.PreviousHash) - fmt.Printf("Hash: %x\n", block.Hash) - fmt.Printf("Nonce: %d\n", block.Nonce) - fmt.Printf("Difficulty: %d\n", block.Difficulty) - fmt.Printf("TimeStamp: %d\n", block.UnixTimeStamp) - fmt.Printf("Transactions: %d\n", len(block.Transaction)) + fmt.Printf("Previous Hash: %x\n", block.PreviousHash) + fmt.Printf("Hash: %x\n", block.Hash) + fmt.Printf("Nonce: %d\n", block.Nonce) + fmt.Printf("Difficulty: %d\n", block.Difficulty) + fmt.Printf("TimeStamp: %d\n", block.UnixTimeStamp) + fmt.Printf("Transactions:\n") + + for _, tx := range block.Transaction { + fmt.Println(tx) + } + fmt.Printf("E==========%d==========E\n", block.Index) } } func (cli *CLI) createBlockchain(address string) { + if !wallet.ValidateAddress(address) { + fmt.Printf("%s is not a valid address\n", address) + logging.ErrorLogger.Printf("%s is not a valid address", address) + runtime.Goexit() + } + bc.InitBlockchain(address) bc.Database.Close() fmt.Println("Success!") } func (cli *CLI) getBalance(address string) { + if !wallet.ValidateAddress(address) { + fmt.Printf("%s is not a valid address\n", address) + logging.ErrorLogger.Printf("%s is not a valid address", address) + runtime.Goexit() + } + bc.ContinueBlockchain(address) defer bc.Database.Close() balance := 0 - UTXOs := bc.HandleUnspentTxs(address) + + finalHash := wallet.Base58Encode([]byte(address)) + publicKeyHash := finalHash[1 : len(finalHash)-4] + + UTXOs := bc.HandleUnspentTxs(publicKeyHash) for _, out := range UTXOs { balance += out.Value @@ -77,6 +98,17 @@ func (cli *CLI) getBalance(address string) { } func (cli *CLI) send(from, to string, amount int) { + if !wallet.ValidateAddress(to) { + fmt.Printf("%s is not a valid address\n", to) + logging.ErrorLogger.Printf("%s is not a valid address", to) + runtime.Goexit() + } + if !wallet.ValidateAddress(from) { + fmt.Printf("%s is not a valid address\n", from) + logging.ErrorLogger.Printf("%s is not a valid address", from) + runtime.Goexit() + } + bc.ContinueBlockchain(from) defer bc.Database.Close() diff --git a/wallet/wallet.go b/wallet/wallet.go index d619761..9adc6e4 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1,6 +1,7 @@ package wallet import ( + "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -77,3 +78,17 @@ func (w Wallet) Address() []byte { return address } + +func ValidateAddress(address string) bool { + fullHash, err := Base58Decode([]byte(address)) + if err != nil { + return false + } + + actualChecksum := fullHash[len(fullHash)-checksumLen:] + version := fullHash[0] + pubKeyHash := fullHash[1 : len(fullHash)-checksumLen] + targetChecksum := Checksum(append([]byte{version}, pubKeyHash...)) + + return bytes.Compare(actualChecksum, targetChecksum) == 0 +}