Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
34fb433
build: Go
KitsuneSemCalda Feb 27, 2026
86e4145
feat: structures
KitsuneSemCalda Feb 27, 2026
6e2f830
feat: blockchain_node
KitsuneSemCalda Feb 27, 2026
72a979c
feat: blockchain
KitsuneSemCalda Feb 27, 2026
efa9bf5
feat(p2p): add a initial p2p server to communicate between nodes
KitsuneSemCalda Feb 28, 2026
871328a
build(deps): add libp2p as dependency
KitsuneSemCalda Feb 28, 2026
096ba07
feat(blockchain): add a method to get all blocks
KitsuneSemCalda Feb 28, 2026
43fa2b0
chore(gitignore): remove compiled version of repository
KitsuneSemCalda Feb 28, 2026
aebe06a
build(make): add a Makefile to build the blockchain
KitsuneSemCalda Feb 28, 2026
fcd844c
feat(main): add two binaries from client and daemon
KitsuneSemCalda Feb 28, 2026
5f03a7c
feat(p2p): add callbacks to network
KitsuneSemCalda Feb 28, 2026
0bf54ef
test(gest): add gest as default tester
KitsuneSemCalda Feb 28, 2026
d0f72d6
test(gest): add tester initial config
KitsuneSemCalda Feb 28, 2026
e0b3280
build(make): use make to run tests
KitsuneSemCalda Feb 28, 2026
4763f05
feat(cmd): try initialize the common server between nodes
KitsuneSemCalda Feb 28, 2026
b35ac9b
ci(tester): run tester on CI
KitsuneSemCalda Feb 28, 2026
ee69799
refactor(mv): improve the order of source code
KitsuneSemCalda Feb 28, 2026
d093643
chore(gitignore): ignore sqlite3 db
KitsuneSemCalda Feb 28, 2026
038349d
feat: implementation of robust block validation, chain reorg, and com…
KitsuneSemCalda Mar 1, 2026
e7ccc21
feat: implementation of centralized logging, failed peers cache, and …
KitsuneSemCalda Mar 2, 2026
6426a8a
Merge branch 'master' into dev
KitsuneSemCalda Mar 2, 2026
6b66875
test(sbc): update tests to work properly
KitsuneSemCalda Mar 2, 2026
a142732
fix(dep): remove unused dependencie 'time'
KitsuneSemCalda Mar 2, 2026
18ca62e
feat(cli): standardize logging and improve SBC TUI
KitsuneSemCalda Mar 2, 2026
5e16b67
Merge branch 'master' into dev
KitsuneSemCalda Mar 2, 2026
9c04cbf
Add log package import to server.go
KitsuneSemCalda Mar 2, 2026
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
146 changes: 94 additions & 52 deletions cmd/sbc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bufio"
"context"
"fmt"
"log"
"os"
"strconv"
"strings"
Expand All @@ -13,22 +12,37 @@ import (
"KitsuneSemCalda/SBC/internal/p2p"
)

const (
ColorReset = "\033[0m"
ColorRed = "\033[31m"
ColorGreen = "\033[32m"
ColorYellow = "\033[33m"
ColorBlue = "\033[34m"
ColorPurple = "\033[35m"
ColorCyan = "\033[36m"
ColorGray = "\033[90m"
)

func main() {
bc := blockchain.NewBlockchain()
cfg := p2p.DefaultConfig()
cfg.ParseFlags()

// Set log level to Warn by default to keep the UI clean
p2p.SetLogLevel(p2p.LevelWarn)

server, err := p2p.NewServer(cfg, bc)
if err != nil {
log.Fatalf("failed to create server: %v", err)
fmt.Printf("%sError: failed to create server: %v%s\n", ColorRed, err, ColorReset)
os.Exit(1)
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func() {
if err := server.Start(ctx); err != nil {
log.Printf("Server error: %v", err)
p2p.Error("Server", "Server error: %v", err)
}
}()

Expand All @@ -37,19 +51,16 @@ func main() {
continue
}
if err := server.ConnectToPeer(addr); err != nil {
log.Printf("Can't connect to peer %s: %v", addr, err)
p2p.Warn("P2P", "Can't connect to peer %s: %v", addr, err)
}
}

reader := bufio.NewReader(os.Stdin)

fmt.Println("Simple Blockchain CLI (P2P Enabled)")
fmt.Printf("Node ID: %s\n", server.GetHostID())
fmt.Printf("Listening on: %s\n", server.GetAddrs())
fmt.Println("Commands: add <bpm>, print, validate, length, peers, addr, connect <addr>, sync, discover, find <hash>, help, quit")
printHeader(server)

for {
fmt.Print("> ")
fmt.Printf("%sSBC %s>%s ", ColorBlue, ColorCyan, ColorReset)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "" {
Expand All @@ -60,70 +71,77 @@ func main() {
args := parts[1:]
switch command {
case "add":
if len(parts) < 2 {
if len(args) < 1 {
fmt.Println("Usage: add <bpm>")
continue
}
bpm, err := strconv.Atoi(parts[1])
bpm, err := strconv.Atoi(args[0])
if err != nil {
fmt.Println("Error: BPM must be a number")
continue
}
bc.AddBlock(bpm)
fmt.Printf("Block added and broadcasting...\n")
fmt.Printf("%s[+] Block added and broadcasting...%s\n", ColorGreen, ColorReset)
case "print":
bc.Print()
limit := 10
if len(args) > 0 {
if l, err := strconv.Atoi(args[0]); err == nil {
limit = l
}
}
blocks := bc.GetAllBlocks()
start := 0
if len(blocks) > limit {
start = len(blocks) - limit
}
fmt.Printf("%s--- Last %d blocks (total: %d) ---%s\n", ColorYellow, len(blocks)-start, len(blocks), ColorReset)
for i := start; i < len(blocks); i++ {
b := blocks[i]
fmt.Printf("%s[%d]%s BPM: %s%d%s Hash: %s%s%s\n",
ColorGray, b.Index, ColorReset,
ColorCyan, b.BPM, ColorReset,
ColorYellow, b.Hash[:12], ColorReset)
}
case "validate":
if bc.IsValid() {
fmt.Println("Blockchain is valid!")
fmt.Printf("%sBlockchain is valid!%s\n", ColorGreen, ColorReset)
} else {
fmt.Println("Blockchain is INVALID!")
fmt.Printf("%sBlockchain is INVALID!%s\n", ColorRed, ColorReset)
}
case "length":
fmt.Printf("Blockchain length: %d\n", bc.Length())
fmt.Printf("Blockchain length: %s%d%s\n", ColorCyan, bc.Length(), ColorReset)
case "peers":
peers := server.GetPeers()
if len(peers) == 0 {
fmt.Println("No peers connected")
continue
}
fmt.Printf("Connected peers (%d):\n", len(peers))
fmt.Printf("%sConnected peers (%d):%s\n", ColorYellow, len(peers), ColorReset)
for id, p := range peers {
fmt.Printf(" - %s (height: %d)\n", id, p.BestHeight)
fmt.Printf(" - %s%s%s (height: %s%d%s)\n", ColorCyan, id.String()[:12], ColorReset, ColorYellow, p.BestHeight, ColorReset)
}
case "addr":
addrs := server.GetAddrs()
nodeID := server.GetHostID()
fmt.Println("Your full addresses (share these to connect):")
for _, addr := range addrs {
fmt.Printf(" %s/p2p/%s\n", addr, nodeID)
fmt.Printf(" %s%s/p2p/%s%s\n", ColorCyan, addr, nodeID, ColorReset)
}
case "connect":
if len(args) < 1 {
fmt.Println("Usage: connect <multiaddr>")
fmt.Println("Example: connect /ip4/127.0.0.1/tcp/8333/p2p/Qm...")
continue
}
addr := args[0]
if err := server.ConnectToPeer(addr); err != nil {
fmt.Printf("Error connecting to peer: %v\n", err)
fmt.Printf("%sError connecting to peer: %v%s\n", ColorRed, err, ColorReset)
} else {
fmt.Printf("Connected to %s\n", addr)
fmt.Printf("%sConnected to %s%s\n", ColorGreen, addr, ColorReset)
}
case "sync":
peers := server.GetPeers()
if len(peers) == 0 {
fmt.Println("No peers connected to sync")
continue
}
fmt.Println("Requesting blocks from peers...")
server.RequestSync()
case "discover":
peers := server.GetPeers()
if len(peers) == 0 {
fmt.Println("No peers connected to discover more")
continue
}
fmt.Println("Discovering more peers...")
server.DiscoverPeers()
case "find":
Expand All @@ -132,33 +150,57 @@ func main() {
continue
}
hash := args[0]
peers := server.GetPeers()
if len(peers) == 0 {
fmt.Println("No peers connected to search")
continue
}
fmt.Printf("Searching for block: %s\n", hash)
server.FindBlock(hash)
case "log":
if len(args) < 1 {
fmt.Println("Usage: log <debug|info|warn|error|none>")
continue
}
level := strings.ToLower(args[0])
switch level {
case "debug": p2p.SetLogLevel(p2p.LevelDebug)
case "info": p2p.SetLogLevel(p2p.LevelInfo)
case "warn": p2p.SetLogLevel(p2p.LevelWarn)
case "error": p2p.SetLogLevel(p2p.LevelError)
case "none": p2p.SetLogLevel(p2p.LevelNone)
default: fmt.Println("Unknown level. Use: debug, info, warn, error, none")
}
fmt.Printf("Log level set to: %s\n", level)
case "help":
fmt.Println("Available commands:")
fmt.Println(" add <bpm> - Add a new block with given BPM")
fmt.Println(" print - Print all blocks in the blockchain")
fmt.Println(" validate - Validate blockchain integrity")
fmt.Println(" length - Show blockchain length")
fmt.Println(" peers - Show connected peers")
fmt.Println(" addr - Show your full addresses to share")
fmt.Println(" connect <addr> - Connect to a peer via multiaddr (use 'addr' on other node)")
fmt.Println(" sync - Request blocks from connected peers")
fmt.Println(" discover - Discover more peers from connected peers")
fmt.Println(" find <hash> - Find a block by hash in the network")
fmt.Println(" help - Show this help message")
fmt.Println(" quit - Exit the program")
printHelp()
case "quit", "exit":
fmt.Println("Goodbye!")
return
default:
fmt.Printf("Unknown command: %s\n", command)
fmt.Println("Available commands: add, print, validate, length, peers, addr, connect, sync, discover, find, help, quit")
fmt.Printf("Unknown command: %s. Type 'help' for commands.\n", command)
}
}
}

func printHeader(server *p2p.Server) {
fmt.Printf("%s----------------------------------------%s\n", ColorBlue, ColorReset)
fmt.Printf("%s Simple Blockchain CLI (P2P Enabled)%s\n", ColorCyan, ColorReset)
fmt.Printf("%s----------------------------------------%s\n", ColorBlue, ColorReset)
fmt.Printf("Node ID: %s%s%s\n", ColorYellow, server.GetHostID(), ColorReset)
fmt.Printf("Listening on: %s%v%s\n", ColorYellow, server.GetAddrs(), ColorReset)
fmt.Printf("Type %s'help'%s for a list of commands.\n", ColorGreen, ColorReset)
fmt.Println()
}

func printHelp() {
fmt.Println("Available commands:")
fmt.Printf(" %sadd <bpm>%s - Add a new block with given BPM\n", ColorCyan, ColorReset)
fmt.Printf(" %sprint [n]%s - Print last n blocks (default 10)\n", ColorCyan, ColorReset)
fmt.Printf(" %svalidate%s - Validate blockchain integrity\n", ColorCyan, ColorReset)
fmt.Printf(" %slength%s - Show blockchain length\n", ColorCyan, ColorReset)
fmt.Printf(" %speers%s - Show connected peers\n", ColorCyan, ColorReset)
fmt.Printf(" %saddr%s - Show your full addresses to share\n", ColorCyan, ColorReset)
fmt.Printf(" %sconnect <addr>%s - Connect to a peer via multiaddr\n", ColorCyan, ColorReset)
fmt.Printf(" %ssync%s - Request blocks from connected peers\n", ColorCyan, ColorReset)
fmt.Printf(" %sdiscover%s - Discover more peers\n", ColorCyan, ColorReset)
fmt.Printf(" %sfind <hash>%s - Find a block by hash\n", ColorCyan, ColorReset)
fmt.Printf(" %slog <level>%s - Set log level (debug, info, warn, error, none)\n", ColorCyan, ColorReset)
fmt.Printf(" %shelp%s - Show this help message\n", ColorCyan, ColorReset)
fmt.Printf(" %squit%s - Exit the program\n", ColorCyan, ColorReset)
}
33 changes: 15 additions & 18 deletions cmd/sbcd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package main
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
Expand All @@ -31,15 +29,15 @@ func min(a, b int) int {
}

func (c *DaemonCallbacks) OnNewPeer(id peer.ID) {
logger.Info("new peer connected", "peer", id.String()[:min(16, len(id.String()))])
p2p.Info("SBCD", "new peer connected: %s", id.String()[:min(16, len(id.String()))])
}

func (c *DaemonCallbacks) OnDisconnect(id peer.ID) {
logger.Warn("peer disconnected", "peer", id.String()[:min(16, len(id.String()))])
p2p.Warn("SBCD", "peer disconnected: %s", id.String()[:min(16, len(id.String()))])
}

func (c *DaemonCallbacks) OnPeerFound(info peer.AddrInfo) {
logger.Debug("peer found via discovery", "peer", info.ID.String()[:min(16, len(info.ID.String()))])
p2p.Debug("SBCD", "peer found via discovery: %s", info.ID.String()[:min(16, len(info.ID.String()))])
}

func (c *DaemonCallbacks) OnBlockReceived(block *blockchain.Block) {
Expand Down Expand Up @@ -90,8 +88,9 @@ func (h *ColoredHandler) Handle(ctx context.Context, r slog.Record) error {
}

func main() {
initLogger()
logger.Info("starting sbc daemon")
// Daemon defaults to Info level
p2p.SetLogLevel(p2p.LevelInfo)
p2p.Info("SBCD", "starting sbc daemon")

bc := blockchain.NewBlockchain()
cfg := p2p.DefaultConfig()
Expand All @@ -101,20 +100,20 @@ func main() {

store, err := storage.NewStore(cfg.DataDir)
if err != nil {
logger.Error("failed to open database", "error", err)
p2p.Error("SBCD", "failed to open database: %v", err)
os.Exit(1)
}
defer store.Close()

err = store.Load(bc)
if err != nil {
logger.Error("failed to load blockchain from store", "error", err)
p2p.Error("SBCD", "failed to load blockchain from store: %v", err)
os.Exit(1)
}

server, err := p2p.NewServer(cfg, bc)
if err != nil {
logger.Error("failed to create server", "error", err)
p2p.Error("SBCD", "failed to create server: %v", err)
os.Exit(1)
}

Expand All @@ -128,7 +127,7 @@ func main() {
}
err = server.ConnectToPeer(addr)
if err != nil {
logger.Warn("can't connect to bootnode", "address", addr, "error", err)
p2p.Warn("SBCD", "can't connect to bootnode %s: %v", addr, err)
}
}

Expand All @@ -144,17 +143,15 @@ func main() {
<-stop
err := store.Save(bc)
if err != nil {
logger.Error("failed to save blockchain", "error", err)
p2p.Error("SBCD", "failed to save blockchain: %v", err)
} else {
logger.Info("blockchain saved successfully")
p2p.Info("SBCD", "blockchain saved successfully")
}
cancel()
}()

logger.Info("daemon initialized",
"peer_id", server.GetHostID(),
"listening", server.GetAddrs(),
"height", bc.Length())
p2p.Info("SBCD", "daemon initialized: peer_id=%s listening=%v height=%d",
server.GetHostID(), server.GetAddrs(), bc.Length())

announceData := map[string]string{
"peer_id": server.GetHostID(),
Expand All @@ -166,6 +163,6 @@ func main() {

err = server.Start(ctx)
if err != nil {
logger.Error("server error", "error", err)
p2p.Error("SBCD", "server error: %v", err)
}
}
Loading