Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Go Tests

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'

- name: Install dependencies
run: go mod download

- name: Run tests
run: go run cmd/tester/main.go
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ go.work.sum
# Editor/IDE
# .idea/
# .vscode/
bin
blockchain.db
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Makefile for Simple BlockChain (SBC)

# Variables
APP_NAME_CLI = sbc
APP_NAME_DAEMON = sbcd
BUILD_DIR = bin
CMD_CLI_DIR = ./cmd/sbc
CMD_DAEMON_DIR = ./cmd/sbcd

# Go parameters
GOCMD = go
GOBUILD = $(GOCMD) build
GOCLEAN = $(GOCMD) clean
GOTEST = $(GOCMD) run ./cmd/tester
GOGET = $(GOCMD) get

.PHONY: all build clean test help cli daemon

all: build

build: cli daemon

cli:
@echo "Building CLI client..."
@mkdir -p $(BUILD_DIR)
$(GOBUILD) -o $(BUILD_DIR)/$(APP_NAME_CLI) $(CMD_CLI_DIR)

daemon:
@echo "Building Daemon..."
@mkdir -p $(BUILD_DIR)
$(GOBUILD) -o $(BUILD_DIR)/$(APP_NAME_DAEMON) $(CMD_DAEMON_DIR)

clean:
@echo "Cleaning up..."
$(GOCLEAN)
rm -rf $(BUILD_DIR)

test:
@echo "Running tests..."
$(GOTEST)

help:
@echo "Available commands:"
@echo " make build - Build both CLI and Daemon"
@echo " make cli - Build the CLI client"
@echo " make daemon - Build the Daemon"
@echo " make clean - Remove built binaries"
@echo " make test - Run Go tests"
37 changes: 37 additions & 0 deletions Tests/background/blockchain_bg_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package background_tests

import (
"KitsuneSemCalda/SBC/internal/blockchain"
"github.com/caiolandgraf/gest/gest"
"sync"
)

func init() {
s := gest.Describe("Blockchain Background")

s.It("should handle rapid block additions", func(t *gest.T) {
bc := blockchain.NewBlockchain()

var wg sync.WaitGroup
numGoroutines := 10
blocksPerGoroutine := 10

for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < blocksPerGoroutine; j++ {
bc.AddBlock(100 + id*10 + j)
}
}(i)
}

wg.Wait()

expectedLength := 1 + (numGoroutines * blocksPerGoroutine)
t.Expect(bc.Length()).ToBe(expectedLength)
t.Expect(bc.IsValid()).ToBeTrue()
})

gest.Register(s)
}
38 changes: 38 additions & 0 deletions Tests/background/p2p_bg_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package background_tests

import (
"KitsuneSemCalda/SBC/internal/blockchain"
"KitsuneSemCalda/SBC/internal/p2p"
"github.com/caiolandgraf/gest/gest"
"sync"
)

func init() {
s := gest.Describe("P2P Background")

s.It("should handle rapid block broadcasts concurrently", func(t *gest.T) {
bc := blockchain.NewBlockchain()
cfg := p2p.DefaultConfig()
cfg.ListenAddr = "/ip4/127.0.0.1/tcp/0"

server, _ := p2p.NewServer(cfg, bc)
defer server.Close()

var wg sync.WaitGroup
numBroadcasters := 5

for i := 0; i < numBroadcasters; i++ {
wg.Add(1)
go func(bpm int) {
defer wg.Done()
block := blockchain.NewBlock(1, bpm, bc.GetLastBlock().Hash)
server.BroadcastBlock(block)
}(100 + i)
}

wg.Wait()
t.Expect(len(server.GetPeers())).ToBe(0) // No peers to broadcast to, but should not crash
})

gest.Register(s)
}
44 changes: 44 additions & 0 deletions Tests/background/storage_bg_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package background_tests

import (
"KitsuneSemCalda/SBC/internal/blockchain"
"KitsuneSemCalda/SBC/internal/storage"
"github.com/caiolandgraf/gest/gest"
"os"
"sync"
)

func init() {
s := gest.Describe("Storage Background")

testDir := "bg_storage_data"

s.It("should handle rapid store operations concurrently", func(t *gest.T) {
bc := blockchain.NewBlockchain()
store, _ := storage.NewStore(testDir)
defer func() {
store.Close()
os.RemoveAll(testDir)
}()

var wg sync.WaitGroup
numOperations := 10

for i := 0; i < numOperations; i++ {
wg.Add(1)
go func(bpm int) {
defer wg.Done()
bc.AddBlock(bpm)
store.Save(bc)
}(100 + i)
}

wg.Wait()

newBC := blockchain.NewBlockchain()
store.Load(newBC)
t.Expect(newBC.Length()).Not().ToBe(0)
})

gest.Register(s)
}
40 changes: 40 additions & 0 deletions Tests/e2e/blockchain_e2e_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package e2e_tests

import (
"KitsuneSemCalda/SBC/internal/blockchain"
"github.com/caiolandgraf/gest/gest"
)

func init() {
s := gest.Describe("Blockchain E2E")

s.It("should replace chain with longer valid chain", func(t *gest.T) {
bc := blockchain.NewBlockchain()

// Create a separate longer chain
newBC := blockchain.NewBlockchain()
newBC.AddBlock(100)
newBC.AddBlock(110)
newBC.AddBlock(120)

longerBlocks := newBC.GetAllBlocks()

success, msg := bc.TryAcceptChain(longerBlocks)
t.Expect(success).ToBeTrue()
t.Expect(msg).ToBe("chain replaced with longer version")
t.Expect(bc.Length()).ToBe(4)
})

s.It("should validate chain with multiple blocks", func(t *gest.T) {
bc := blockchain.NewBlockchain()
bc.AddBlock(100)
bc.AddBlock(110)

blocks := bc.GetAllBlocks()
valid, msg := bc.ValidateChain(blocks)
t.Expect(valid).ToBeTrue()
t.Expect(msg).ToBe("")
})

gest.Register(s)
}
47 changes: 47 additions & 0 deletions Tests/e2e/p2p_e2e_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package e2e_tests

import (
"KitsuneSemCalda/SBC/internal/blockchain"
"KitsuneSemCalda/SBC/internal/p2p"
"github.com/caiolandgraf/gest/gest"
"time"
)

func init() {
s := gest.Describe("P2P E2E")

s.It("should synchronize blocks between two nodes", func(t *gest.T) {
bc1 := blockchain.NewBlockchain()
bc2 := blockchain.NewBlockchain()

cfg1 := p2p.DefaultConfig()
cfg1.ListenAddr = "/ip4/127.0.0.1/tcp/0"

server1, _ := p2p.NewServer(cfg1, bc1)

cfg2 := p2p.DefaultConfig()
cfg2.ListenAddr = "/ip4/127.0.0.1/tcp/0"
server2, _ := p2p.NewServer(cfg2, bc2)

// Add block to node 1
bc1.AddBlock(100)
t.Expect(bc1.Length()).ToBe(2)

// Manually connect server2 to server1
addr1 := server1.GetAddrs()[0].String() + "/p2p/" + server1.GetHostID()
err := server2.ConnectToPeer(addr1)
t.Expect(err).ToBeNil()

// Give some time for the handshake to complete asynchronously
time.Sleep(100 * time.Millisecond)

// Verify they are connected
t.Expect(len(server1.GetPeers())).Not().ToBe(0)
t.Expect(len(server2.GetPeers())).Not().ToBe(0)

server1.Close()
server2.Close()
})

gest.Register(s)
}
38 changes: 38 additions & 0 deletions Tests/e2e/storage_e2e_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package e2e_tests

import (
"KitsuneSemCalda/SBC/internal/blockchain"
"KitsuneSemCalda/SBC/internal/storage"
"github.com/caiolandgraf/gest/gest"
"os"
)

func init() {
s := gest.Describe("Storage E2E")

testDir := "e2e_storage_data"

s.It("should store and retrieve a complete chain after restart", func(t *gest.T) {
bc := blockchain.NewBlockchain()
bc.AddBlock(100)
bc.AddBlock(200)
bc.AddBlock(300)

store, _ := storage.NewStore(testDir)
store.Save(bc)
store.Close()

// Simulate restart
newBC := blockchain.NewBlockchain()
newStore, _ := storage.NewStore(testDir)
newStore.Load(newBC)

t.Expect(newBC.Length()).ToBe(4)
t.Expect(newBC.GetLastBlock().BPM).ToBe(300)

newStore.Close()
os.RemoveAll(testDir)
})

gest.Register(s)
}
Loading