Skip to content

Add transactions #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 145 additions & 7 deletions cmd/gochain/main.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,160 @@
package main

import (
"bufio"
"encoding/hex"
"fmt"
"github.com/NicholasRodrigues/go-chain/internal/blockchain"
"github.com/NicholasRodrigues/go-chain/internal/transactions"
"github.com/NicholasRodrigues/go-chain/pkg/crypto"
"os"
"strconv"
)

func main() {
bc := blockchain.NewBlockchain()
scanner := bufio.NewScanner(os.Stdin)

bc.AddBlock("First Block after Genesis")
bc.AddBlock("Second Block after Genesis")
for {
printMenu()
scanner.Scan()
cmd := scanner.Text()

for _, block := range bc.Blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
switch cmd {
case "1":
handleAddBlock(bc, scanner)
case "2":
handleViewBlockchain(bc)
case "3":
handleValidateBlockchain(bc)
case "4":
handleCreateTransaction(scanner)
case "5":
handleValidateTransaction(scanner)
case "6":
handleExit()
default:
fmt.Println("Invalid command. Please try again.")
}
}
}

func printMenu() {
fmt.Println("\nBlockchain CLI")
fmt.Println("1. Add Block")
fmt.Println("2. View Blockchain")
fmt.Println("3. Validate Blockchain")
fmt.Println("4. Create Transaction")
fmt.Println("5. Validate Transaction")
fmt.Println("6. Exit")
fmt.Print("Enter command: ")
}

func handleAddBlock(bc *blockchain.Blockchain, scanner *bufio.Scanner) {
fmt.Print("Enter data for the new block: ")
scanner.Scan()
data := scanner.Text()

input := func() string {
return "input data"
}
receive := func() string {
return data
}

blockchain.InputContributionFunction([]byte(data), bc, len(bc.Blocks), input, receive)
fmt.Println("Block added successfully!")
}

func handleViewBlockchain(bc *blockchain.Blockchain) {
for i, block := range bc.Blocks {
fmt.Printf("Block %d: %x\n", i, block.Hash)
fmt.Printf("Previous Hash: %x\n", block.PrevBlockHash)
fmt.Printf("Transactions: \n")
for _, tx := range block.Transactions {
fmt.Printf(" %s\n", tx.String())
}
fmt.Println()
}
}

func handleValidateBlockchain(bc *blockchain.Blockchain) {
if blockchain.ChainValidationPredicate(bc) {
fmt.Println("Blockchain is valid.")
} else {
fmt.Println("Blockchain is invalid.")
}
}

func handleCreateTransaction(scanner *bufio.Scanner) {
fmt.Print("Enter Txid: ")
scanner.Scan()
txid := scanner.Text()

fmt.Print("Enter Vout: ")
scanner.Scan()
vout, _ := strconv.Atoi(scanner.Text())

fmt.Print("Enter ScriptSig: ")
scanner.Scan()
scriptSig := scanner.Text()

fmt.Print("Enter Value: ")
scanner.Scan()
value, _ := strconv.Atoi(scanner.Text())

fmt.Print("Enter ScriptPubKey: ")
scanner.Scan()
scriptPubKey := scanner.Text()

tx := transactions.NewTransaction(
[]transactions.TransactionInput{{Txid: []byte(txid), Vout: vout, ScriptSig: scriptSig}},
[]transactions.TransactionOutput{{Value: value, ScriptPubKey: scriptPubKey}},
)

privKey, _ := crypto.NewPrivateKey()
tx.Sign(privKey)

fmt.Println("Transaction created successfully!")
fmt.Println(tx.String())
}

func handleValidateTransaction(scanner *bufio.Scanner) {
fmt.Print("Enter serialized transaction: ")
scanner.Scan()
serializedTx := scanner.Text()
txBytes, _ := hex.DecodeString(serializedTx)
tx := transactions.DeserializeTransaction(txBytes)

utxoSet := make(map[string]transactions.TransactionOutput)
fmt.Println("Enter UTXO set (enter 'done' to finish):")
for {
fmt.Print("Enter UTXO key (txid:vout): ")
scanner.Scan()
key := scanner.Text()
if key == "done" {
break
}

fmt.Print("Enter value: ")
scanner.Scan()
value, _ := strconv.Atoi(scanner.Text())

fmt.Print("Enter ScriptPubKey: ")
scanner.Scan()
scriptPubKey := scanner.Text()

utxoSet[key] = transactions.TransactionOutput{Value: value, ScriptPubKey: scriptPubKey}
}

if tx.Validate(utxoSet) {
fmt.Println("Transaction is valid.")
} else {
fmt.Println("Transaction is invalid.")
}
}

fmt.Println("Is blockchain valid?", bc.IsValid())
func handleExit() {
fmt.Println("Exiting...")
os.Exit(0)
}
23 changes: 19 additions & 4 deletions internal/blockchain/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,45 @@ package blockchain
import (
"bytes"
"crypto/sha256"
"github.com/NicholasRodrigues/go-chain/internal/transactions"
"strconv"
"time"
)

type Block struct {
Timestamp int64
Data []byte // Transactions
Transactions []*transactions.Transaction
PrevBlockHash []byte
Hash []byte
Counter int // Nonce
}

// SetHash recalculates the hash of the block.
func (b *Block) SetHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
headers := bytes.Join([][]byte{b.PrevBlockHash, b.HashTransactions(), timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
}

// HashTransactions returns a hash of the transactions in the block.
func (b *Block) HashTransactions() []byte {
var txHashes [][]byte
var txHash [32]byte

for _, tx := range b.Transactions {
txHashes = append(txHashes, tx.Serialize())
}
txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))

return txHash[:]
}

// NewBlock creates and returns a new block.
func NewBlock(data string, prevBlockHash []byte) *Block {
func NewBlock(transactions []*transactions.Transaction, prevBlockHash []byte) *Block {
block := &Block{
Timestamp: time.Now().Unix(),
Data: []byte(data),
Transactions: transactions,
PrevBlockHash: prevBlockHash,
Hash: []byte{},
Counter: 0,
Expand Down
75 changes: 36 additions & 39 deletions internal/blockchain/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,51 @@ package blockchain

import (
"bytes"
"crypto/sha256"
"strconv"
"github.com/NicholasRodrigues/go-chain/internal/transactions"
"github.com/NicholasRodrigues/go-chain/pkg/crypto"
"testing"
"time"
)

func TestSetHash(t *testing.T) {
data := "test data"
prevHash := []byte("previous hash")
block := &Block{
Timestamp: time.Now().Unix(),
Data: []byte(data),
PrevBlockHash: prevHash,
Counter: 0,
}
block.SetHash()

expectedHash := sha256.Sum256(bytes.Join([][]byte{prevHash, []byte(data), []byte(strconv.FormatInt(block.Timestamp, 10))}, []byte{}))
if !bytes.Equal(block.Hash, expectedHash[:]) {
t.Errorf("expected hash %x, got %x", expectedHash, block.Hash)
}
func createTransaction() *transactions.Transaction {
privKey, _ := crypto.NewPrivateKey()
tx := transactions.NewTransaction(
[]transactions.TransactionInput{
{Txid: []byte("somepreviousid"), Vout: 0, ScriptSig: "scriptSig"},
},
[]transactions.TransactionOutput{
{Value: 10, ScriptPubKey: "pubkey1"},
},
)
tx.Sign(privKey)
return tx
}

func TestNewBlock(t *testing.T) {
data := "test data"
prevHash := []byte("previous hash")
block := NewBlock(data, prevHash)
// TestBlockHash checks if the block hash is set correctly
func TestBlockHash(t *testing.T) {
tx := createTransaction()
block := NewBlock([]*transactions.Transaction{tx}, []byte{})
expectedHash := block.Hash

if block.Data == nil || !bytes.Equal(block.Data, []byte(data)) {
t.Errorf("expected data %s, got %s", data, string(block.Data))
}
if block.PrevBlockHash == nil || !bytes.Equal(block.PrevBlockHash, prevHash) {
t.Errorf("expected previous hash %x, got %x", prevHash, block.PrevBlockHash)
}
if block.Hash == nil || len(block.Hash) == 0 {
t.Error("expected non-nil and non-empty hash")
if !bytes.Equal(block.Hash, expectedHash) {
t.Errorf("Expected hash %x, but got %x", expectedHash, block.Hash)
}
}

func TestNewGenesisBlock(t *testing.T) {
genesisBlock := NewGenesisBlock()
if genesisBlock == nil {
t.Fatalf("Expected genesis block to be created")
// TestGenesisBlock checks if the genesis block is created correctly
func TestGenesisBlock(t *testing.T) {
block := NewGenesisBlock()
if len(block.PrevBlockHash) != 0 {
t.Errorf("Genesis block should have no previous block hash")
}
if len(genesisBlock.Hash) == 0 {
t.Errorf("Expected genesis block hash to be set")
}
if !bytes.Equal(genesisBlock.PrevBlockHash, []byte{}) {
t.Errorf("Expected genesis block to have no previous block hash")
}

// TestBlockMining ensures that mining a block works correctly
func TestBlockMining(t *testing.T) {
tx := createTransaction()
block := NewBlock([]*transactions.Transaction{tx}, []byte{})
pow := NewProofOfWork(block)

if !pow.Validate() {
t.Errorf("Block did not pass proof of work validation")
}
}
22 changes: 18 additions & 4 deletions internal/blockchain/blockchain.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
package blockchain

import "bytes"
import (
"bytes"
"github.com/NicholasRodrigues/go-chain/internal/transactions"
)

type Blockchain struct {
Blocks []*Block
}

func (bc *Blockchain) AddBlock(data string) {
// AddBlock adds a new block to the blockchain with the given transactions.
func (bc *Blockchain) AddBlock(transactions []*transactions.Transaction) {
prevBlock := bc.Blocks[len(bc.Blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
newBlock := NewBlock(transactions, prevBlock.Hash)
bc.Blocks = append(bc.Blocks, newBlock)
}

// NewGenesisBlock creates and returns the genesis block.
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
coinbase := transactions.NewTransaction(
[]transactions.TransactionInput{
{Txid: []byte{}, Vout: -1, ScriptSig: "Genesis"},
},
[]transactions.TransactionOutput{
{Value: 50, ScriptPubKey: "coinbase"},
},
)
return NewBlock([]*transactions.Transaction{coinbase}, []byte{})
}

// NewBlockchain creates and returns a new blockchain with the genesis block.
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
Expand Down
Loading
Loading