Compare commits
4 Commits
main
...
go-rewrite
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99c5dba721 | ||
|
|
23f6dba3f3 | ||
|
|
d7338f717b | ||
|
|
59adb32e68 |
4
server/.gitignore
vendored
4
server/.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
# Go files
|
||||
ccoin
|
||||
go.sum
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
|
||||
0
server/Dockerfile
Normal file
0
server/Dockerfile
Normal file
49
server/Makefile
Normal file
49
server/Makefile
Normal file
@@ -0,0 +1,49 @@
|
||||
# Go parameters
|
||||
GOCMD=go
|
||||
GOBUILD=$(GOCMD) build
|
||||
GOCLEAN=$(GOCMD) clean
|
||||
GOTEST=$(GOCMD) test
|
||||
GOGET=$(GOCMD) get
|
||||
GOMOD=$(GOCMD) mod
|
||||
GOFMT=gofmt
|
||||
GOLINT=golangci-lint
|
||||
|
||||
# Binary names
|
||||
BINARY_NAME=ccoin
|
||||
BINARY_LINUX=$(BINARY_NAME)-linux-x64
|
||||
BINARY_MAC=$(BINARY_NAME)-macos-arm64
|
||||
BINARY_WINDOWS=$(BINARY_NAME)-windows-x64.exe
|
||||
|
||||
# Build directory
|
||||
BUILD_DIR=build
|
||||
|
||||
# Version info (can be overridden)
|
||||
VERSION?=1.0.0
|
||||
BUILD_TIME?=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
COMMIT?=$(shell git rev-parse HEAD)
|
||||
|
||||
# Linker flags to embed version info
|
||||
LDFLAGS=-ldflags "-X ccoin/config/server.version=$(VERSION) -X ccoin/config/server.buildTime=$(BUILD_TIME) -X ccoin/config/server.commit=$(COMMIT) -s -w" -trimpath
|
||||
|
||||
.PHONY: all build clean test coverage deps fmt lint vet help
|
||||
.PHONY: build-linux build-windows build-darwin run dev
|
||||
.PHONY: docker-build install uninstall
|
||||
|
||||
# Default target
|
||||
all: clean deps fmt vet test build
|
||||
|
||||
# Build the binary
|
||||
build:
|
||||
$(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) -v
|
||||
|
||||
# Build for Linux
|
||||
build-linux:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_LINUX) -v
|
||||
|
||||
# Build for MacOS
|
||||
build-macos:
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_MAC) -v
|
||||
|
||||
# Build for windows
|
||||
build-windows:
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_WINDOWS) -v
|
||||
207
server/config/database/config.go
Normal file
207
server/config/database/config.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
"ccoin/utils"
|
||||
"github.com/charmbracelet/log"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"os"
|
||||
)
|
||||
|
||||
type DatabaseConfig struct {
|
||||
DB *sql.DB
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// Holds database configuration
|
||||
type DatabaseSettings struct {
|
||||
URL string
|
||||
User string
|
||||
Password string
|
||||
MaxPoolSize int
|
||||
MinIdle int
|
||||
ConnTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
MaxLifetime time.Duration
|
||||
ValidationQuery string
|
||||
}
|
||||
|
||||
// Creates and initializes a new database configuration
|
||||
func NewDatabaseConfig() *DatabaseConfig {
|
||||
return &DatabaseConfig{
|
||||
logger: log.New(os.Stdout),
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes the database connection
|
||||
func (dc *DatabaseConfig) Init() error {
|
||||
dc.logger.Info("Initializing database connection...")
|
||||
|
||||
settings := dc.loadSettings()
|
||||
|
||||
// Create connection string
|
||||
connStr := fmt.Sprintf("%s?user=%s&password=%s", settings.URL, settings.User, settings.Password)
|
||||
|
||||
// Open database connection
|
||||
db, err := sql.Open("pgx", connStr)
|
||||
if err != nil {
|
||||
dc.logger.Error("Failed to open database connection", "error", err)
|
||||
return fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
// Configure database connection pool
|
||||
db.SetMaxOpenConns(settings.MaxPoolSize)
|
||||
db.SetMaxIdleConns(settings.MinIdle)
|
||||
db.SetConnMaxLifetime(settings.MaxLifetime)
|
||||
db.SetConnMaxIdleTime(settings.IdleTimeout)
|
||||
|
||||
// Test the connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), settings.ConnTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
dc.logger.Error("Failed to ping database", "error", err)
|
||||
return fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
dc.DB = db
|
||||
dc.logger.Info("Database connection established successfully")
|
||||
|
||||
// Create tables if they don't exist
|
||||
if err := dc.createTables(); err != nil {
|
||||
return fmt.Errorf("failed to create tables: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loads database settings from environment variables
|
||||
func (dc *DatabaseConfig) loadSettings() DatabaseSettings {
|
||||
return DatabaseSettings{
|
||||
URL: utils.GetEnvOrDefault("DATABASE_URL", "postgres://localhost:5432/ccoin"),
|
||||
User: utils.GetEnvOrDefault("DATABASE_USER", "ccoin"),
|
||||
Password: utils.GetEnvOrDefault("DATABASE_PASSWORD", "ccoin"),
|
||||
MaxPoolSize: utils.GetEnvOrDefault("DATABASE_POOL_SIZE", 20),
|
||||
ConnTimeout: 30 * time.Second,
|
||||
IdleTimeout: 10 * time.Minute,
|
||||
MaxLifetime: 30 * time.Minute,
|
||||
ValidationQuery: "SELECT 1",
|
||||
}
|
||||
}
|
||||
|
||||
// Creates database tables if they don't exist
|
||||
func (dc *DatabaseConfig) createTables() error {
|
||||
dc.logger.Info("Creating database tables if they don't exist...")
|
||||
|
||||
// Define tables
|
||||
tables := []string{
|
||||
// Wallets table
|
||||
`CREATE TABLE IF NOT EXISTS wallets (
|
||||
address VARCHAR(64) PRIMARY KEY,
|
||||
balance DECIMAL(20,8) DEFAULT 0,
|
||||
label VARCHAR(255),
|
||||
password_hash VARCHAR(64),
|
||||
created_at BIGINT NOT NULL,
|
||||
last_activity BIGINT
|
||||
)`,
|
||||
|
||||
// Transactions table
|
||||
`CREATE TABLE IF NOT EXISTS transactions (
|
||||
hash VARCHAR(64) PRIMARY KEY,
|
||||
from_address VARCHAR(64),
|
||||
to_address VARCHAR(64) NOT NULL,
|
||||
amount DECIMAL(20,8) NOT NULL,
|
||||
fee DECIMAL(20,8) DEFAULT 0,
|
||||
memo TEXT,
|
||||
block_hash VARCHAR(64),
|
||||
timestamp BIGINT NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'pending',
|
||||
confirmations INTEGER DEFAULT 0
|
||||
)`,
|
||||
|
||||
// Blocks table
|
||||
`CREATE TABLE IF NOT EXISTS blocks (
|
||||
hash VARCHAR(64) PRIMARY KEY,
|
||||
previous_hash VARCHAR(64),
|
||||
merkle_root VARCHAR(64) NOT NULL,
|
||||
timestamp BIGINT NOT NULL,
|
||||
difficulty INTEGER NOT NULL,
|
||||
nonce BIGINT NOT NULL,
|
||||
miner_address VARCHAR(64) NOT NULL,
|
||||
reward DECIMAL(20,8) NOT NULL,
|
||||
height SERIAL,
|
||||
transaction_count INTEGER DEFAULT 0,
|
||||
confirmations INTEGER DEFAULT 0
|
||||
)`,
|
||||
}
|
||||
|
||||
// Create tables
|
||||
for _, tableSQL := range tables {
|
||||
if _, err := dc.DB.Exec(tableSQL); err != nil {
|
||||
dc.logger.Error("Failed to create table", "error", err, "sql", tableSQL)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
indexes := []string{
|
||||
// Wallets indexes
|
||||
"CREATE INDEX IF NOT EXISTS idx_wallets_created_at ON wallets(created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_wallets_last_activity ON wallets(last_activity)",
|
||||
|
||||
// Transactions indexes
|
||||
"CREATE INDEX IF NOT EXISTS idx_transactions_from_address ON transactions(from_address)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_transactions_to_address ON transactions(to_address)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_transactions_block_hash ON transactions(block_hash)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_transactions_timestamp ON transactions(timestamp)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_transactions_status ON transactions(status)",
|
||||
|
||||
// Blocks indexes
|
||||
"CREATE INDEX IF NOT EXISTS idx_blocks_height ON blocks(height)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_blocks_miner_address ON blocks(miner_address)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_blocks_timestamp ON blocks(timestamp)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_blocks_previous_hash ON blocks(previous_hash)",
|
||||
|
||||
// Foreign key-like indexes for referential integrity
|
||||
"CREATE INDEX IF NOT EXISTS idx_transactions_from_wallet ON transactions(from_address)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_transactions_to_wallet ON transactions(to_address)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_blocks_miner_wallet ON blocks(miner_address)",
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
for _, indexSQL := range indexes {
|
||||
if _, err := dc.DB.Exec(indexSQL); err != nil {
|
||||
dc.logger.Error("Failed to create index", "error", err, "sql", indexSQL)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dc.logger.Info("Database tables and indexes created/verified successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns database connection information
|
||||
func (dc *DatabaseConfig) GetConnectionInfo() map[string]interface{} {
|
||||
settings := dc.loadSettings()
|
||||
return map[string]interface{}{
|
||||
"url": settings.URL,
|
||||
"user": settings.User,
|
||||
"poolSize": settings.MaxPoolSize,
|
||||
}
|
||||
}
|
||||
|
||||
// Closes the database connection
|
||||
func (dc *DatabaseConfig) Close() error {
|
||||
if dc.DB != nil {
|
||||
return dc.DB.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the database connection
|
||||
func (dc *DatabaseConfig) GetDB() *sql.DB {
|
||||
return dc.DB
|
||||
}
|
||||
194
server/config/server/config.go
Normal file
194
server/config/server/config.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/log"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
"ccoin/utils"
|
||||
)
|
||||
|
||||
var version = "dev"
|
||||
var buildTime = time.Now().Format(time.RFC3339)
|
||||
var commit = "none"
|
||||
|
||||
type ServerConfig struct {
|
||||
// App details
|
||||
Version string
|
||||
BuildTime string
|
||||
Commit string
|
||||
BaseURL string
|
||||
|
||||
// Server settings
|
||||
Host string
|
||||
Port int
|
||||
DevelopmentMode bool
|
||||
|
||||
// Mining settings
|
||||
MiningDifficulty int
|
||||
MiningReward float64
|
||||
BlockTimeTarget time.Duration
|
||||
|
||||
// Transaction settings
|
||||
DefaultTransactionFee float64
|
||||
MaxTransactionSize int
|
||||
MaxMemoLength int
|
||||
|
||||
// Security settings
|
||||
JWTSecret string
|
||||
RateLimitRequests int
|
||||
RateLimitWindow time.Duration
|
||||
|
||||
// Blockchain settings
|
||||
MaxBlockSize int
|
||||
MaxTransactionsPerBlock int
|
||||
ConfirmationsRequired int
|
||||
|
||||
// API settings
|
||||
MaxPageSize int
|
||||
DefaultPageSize int
|
||||
APITimeout time.Duration
|
||||
|
||||
// Logging settings
|
||||
LogLevel string
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// Creates and initializes a new ServerConfig
|
||||
func NewServerConfig() *ServerConfig {
|
||||
config := &ServerConfig{
|
||||
logger: log.New(os.Stdout),
|
||||
}
|
||||
|
||||
// Load configuration from environment variables with defaults
|
||||
config.loadConfig()
|
||||
config.logConfig()
|
||||
config.validateConfig()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Loads configuration from environment variables with defaults
|
||||
func (c *ServerConfig) loadConfig() {
|
||||
// App details (these would typically come from build-time variables)
|
||||
c.Version = version
|
||||
c.BuildTime = utils.GetEnvOrDefault("BUILD_TIME", buildTime)
|
||||
c.BaseURL = utils.GetEnvOrDefault("BASE_URL", "http://localhost:8080")
|
||||
c.Commit = commit
|
||||
|
||||
// Server settings
|
||||
c.Host = utils.GetEnvOrDefault("SERVER_HOST", "0.0.0.0")
|
||||
c.Port = utils.GetEnvOrDefault("SERVER_PORT", 8080)
|
||||
c.DevelopmentMode = utils.GetEnvOrDefault("DEVELOPMENT_MODE", true)
|
||||
|
||||
// Mining settings
|
||||
c.MiningDifficulty = utils.GetEnvOrDefault("MINING_DIFFICULTY", 4)
|
||||
c.MiningReward = utils.GetEnvOrDefault("MINING_REWARD", 50.0)
|
||||
c.BlockTimeTarget = time.Duration(utils.GetEnvOrDefault("BLOCK_TIME_TARGET", 600000)) * time.Millisecond // 10 minutes
|
||||
|
||||
// Transaction settings
|
||||
c.DefaultTransactionFee = utils.GetEnvOrDefault("DEFAULT_TRANSACTION_FEE", 5.0)
|
||||
c.MaxMemoLength = utils.GetEnvOrDefault("MAX_MEMO_LENGTH", 256)
|
||||
c.MaxTransactionSize = utils.GetEnvOrDefault("MAX_TRANSACTION_SIZE", 1024*1024) // 1MB
|
||||
|
||||
// Security settings
|
||||
c.JWTSecret = utils.GetEnvOrDefault("JWT_SECRET", "change-this-in-production")
|
||||
c.RateLimitRequests = utils.GetEnvOrDefault("RATE_LIMIT_REQUESTS", 100)
|
||||
c.RateLimitWindow = time.Duration(utils.GetEnvOrDefault("RATE_LIMIT_WINDOW", 60000)) * time.Millisecond // 1 minute
|
||||
|
||||
// Blockchain settings
|
||||
c.MaxBlockSize = utils.GetEnvOrDefault("MAX_BLOCK_SIZE", 1024*1024) // 1MB
|
||||
c.MaxTransactionsPerBlock = utils.GetEnvOrDefault("MAX_TRANSACTIONS_PER_BLOCK", 1000)
|
||||
c.ConfirmationsRequired = utils.GetEnvOrDefault("CONFIRMATIONS_REQUIRED", 6)
|
||||
|
||||
// API Settings
|
||||
c.MaxPageSize = utils.GetEnvOrDefault("MAX_PAGE_SIZE", 100)
|
||||
c.DefaultPageSize = utils.GetEnvOrDefault("DEFAULT_PAGE_SIZE", 50)
|
||||
c.APITimeout = time.Duration(utils.GetEnvOrDefault("API_TIMEOUT", 30000)) * time.Millisecond // 30 seconds
|
||||
|
||||
// Logging settings
|
||||
c.LogLevel = utils.GetEnvOrDefault("LOG_LEVEL", "INFO")
|
||||
}
|
||||
|
||||
// Logs the current configuration
|
||||
func (c *ServerConfig) logConfig() {
|
||||
c.logger.Info("Server configuration loaded:")
|
||||
c.logger.Info("Host", "value", c.Host)
|
||||
c.logger.Info("Port", "value", c.Port)
|
||||
c.logger.Info("Version", "value", c.Version)
|
||||
c.logger.Info("Development mode", "value", c.DevelopmentMode)
|
||||
c.logger.Info("Mining difficulty", "value", c.MiningDifficulty)
|
||||
c.logger.Info("Mining reward", "value", c.MiningReward)
|
||||
c.logger.Info("Block time target", "value", c.BlockTimeTarget)
|
||||
c.logger.Info("Default transaction fee", "value", c.DefaultTransactionFee)
|
||||
c.logger.Info("Confirmations required", "value", c.ConfirmationsRequired)
|
||||
c.logger.Info("Max block size", "value", c.MaxBlockSize)
|
||||
c.logger.Info("Max transactions per block", "value", c.MaxTransactionsPerBlock)
|
||||
c.logger.Info("Max transaction size", "value", c.MaxTransactionSize)
|
||||
c.logger.Info("Log level", "value", c.LogLevel)
|
||||
c.logger.Info("Max page size", "value", c.MaxPageSize)
|
||||
|
||||
if c.JWTSecret == "change-this-in-production" && !c.DevelopmentMode {
|
||||
c.logger.Warn("Using default JWT secret in production mode!")
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a map of server information
|
||||
func (c *ServerConfig) GetServerInfo() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"host": c.Host,
|
||||
"port": c.Port,
|
||||
"development_mode": c.DevelopmentMode,
|
||||
"version": c.Version,
|
||||
"mining_difficulty": c.MiningDifficulty,
|
||||
"mining_reward": c.MiningReward,
|
||||
"block_time_target": c.BlockTimeTarget,
|
||||
"confirmations_required": c.ConfirmationsRequired,
|
||||
}
|
||||
}
|
||||
|
||||
// Validates the configuration values
|
||||
func (c *ServerConfig) ValidateConfig() error {
|
||||
if c.Port < 1 || c.Port > 65535 {
|
||||
return fmt.Errorf("port must be between 1 and 65535")
|
||||
}
|
||||
|
||||
if c.MiningDifficulty < 1 || c.MiningDifficulty > 32 {
|
||||
return fmt.Errorf("mining difficulty must be between 1 and 32")
|
||||
}
|
||||
|
||||
if c.MiningReward <= 0 {
|
||||
return fmt.Errorf("mining reward must be positive")
|
||||
}
|
||||
|
||||
if c.BlockTimeTarget <= 0 {
|
||||
return fmt.Errorf("block time target must be positive")
|
||||
}
|
||||
|
||||
if c.DefaultTransactionFee < 0 {
|
||||
return fmt.Errorf("default transaction fee cannot be negative")
|
||||
}
|
||||
|
||||
if c.ConfirmationsRequired <= 0 {
|
||||
return fmt.Errorf("confirmations required must be positive")
|
||||
}
|
||||
|
||||
if c.MaxPageSize <= 0 {
|
||||
return fmt.Errorf("max page size must be positive")
|
||||
}
|
||||
|
||||
if c.DefaultPageSize <= 0 {
|
||||
return fmt.Errorf("default page size must be positive")
|
||||
}
|
||||
|
||||
c.logger.Info("Server configuration validation passed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wrapper that panics on validation error (similar to Kotlin's require)
|
||||
func (c *ServerConfig) validateConfig() {
|
||||
if err := c.ValidateConfig(); err != nil {
|
||||
// panic(err)
|
||||
c.logger.Fatal("ERROR", "error", err)
|
||||
}
|
||||
}
|
||||
0
server/docker-compose.yml
Normal file
0
server/docker-compose.yml
Normal file
33
server/go.mod
Normal file
33
server/go.mod
Normal file
@@ -0,0 +1,33 @@
|
||||
module ccoin
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/log v0.4.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
)
|
||||
70
server/models/api/model.go
Normal file
70
server/models/api/model.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package api
|
||||
|
||||
type ApiResponse[T any] struct {
|
||||
Success bool `json:"success"`
|
||||
Data *T `json:"data,omitempty"` // Nullable data
|
||||
Error *string `json:"error,omitempty"` // Nullable error message
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Code *string `json:"code,omitempty"`
|
||||
Details map[string]string `json:"details,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type SuccessResponse struct {
|
||||
Message string `json:"message"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type HealthResponse struct {
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
Uptime int64 `json:"uptime"`
|
||||
Database DatabaseHealth `json:"database"`
|
||||
BlockChain BlockChainHealth `json:"blockchain"`
|
||||
}
|
||||
|
||||
type DatabaseHealth struct {
|
||||
Connected bool `json:"connected"`
|
||||
ResponseTime int64 `json:"response_time"`
|
||||
ActiveConnections int `json:"active_connections"`
|
||||
MaxConnections int `json:"max_connections"`
|
||||
}
|
||||
|
||||
type BlockChainHealth struct {
|
||||
LatestBlock int `json:"latest_block"`
|
||||
PendingTransactions int `json:"pending_transactions"`
|
||||
NetworkHashRate float64 `json:"network_hash_rate"`
|
||||
AverageBlockTime int64 `json:"average_block_time"`
|
||||
}
|
||||
|
||||
type PaginationRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
SortBy *string `json:"sort_by,omitempty"`
|
||||
SortOrder SortOrder `json:"sort_order"`
|
||||
}
|
||||
|
||||
type PaginatedResponse[T any] struct {
|
||||
Data []T `json:"data"`
|
||||
Pagination PaginationInfo `json:"pagination"`
|
||||
}
|
||||
|
||||
type PaginationInfo struct {
|
||||
CurrentPage int `json:"current_page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalItems int `json:"total_items"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
HasNext bool `json:"has_next"`
|
||||
HasPrevious bool `json:"has_previous"`
|
||||
}
|
||||
|
||||
type SortOrder string
|
||||
|
||||
const (
|
||||
ASC SortOrder = "ASC"
|
||||
DESC SortOrder = "DESC"
|
||||
)
|
||||
46
server/models/block/model.go
Normal file
46
server/models/block/model.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package block
|
||||
|
||||
type StartMiningRequest struct {
|
||||
MinerAddress string `json:"miner_address"`
|
||||
Difficulty int `json:"difficulty"`
|
||||
}
|
||||
|
||||
type SubmitMiningRequest struct {
|
||||
MinerAddress string `json:"miner_address"`
|
||||
Hash string `json:"hash"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
PreviousHash *string `json:"previous_hash,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type BlockResponse struct {
|
||||
Hash string `json:"hash"`
|
||||
PreviousHash *string `json:"previous_hash,omitempty"`
|
||||
MerkleRoot string `json:"merkle_root"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Difficulty int `json:"difficulty"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
MinerAddress string `json:"miner_address"`
|
||||
Reward float64 `json:"reward"`
|
||||
Height int `json:"height"`
|
||||
TransactionCount int `json:"transaction_count"`
|
||||
Confirmations int `json:"confirmations"`
|
||||
}
|
||||
|
||||
type MiningStatsResponse struct {
|
||||
MinerAddress string `json:"miner_address"`
|
||||
TotalBlocksMined int `json:"total_blocks_mined"`
|
||||
TotalRewardsEarned float64 `json:"total_rewards_earned"`
|
||||
LastBlockMined *int64 `json:"last_block_mined,omitempty"`
|
||||
CurrentDifficulty int `json:"current_difficulty"`
|
||||
}
|
||||
|
||||
type MiningJobResponse struct {
|
||||
JobId string `json:"job_id"`
|
||||
Target string `json:"target"`
|
||||
Difficulty int `json:"difficulty"`
|
||||
PreviousHash string `json:"previous_hash"`
|
||||
Height int `json:"height"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
}
|
||||
39
server/models/transaction/model.go
Normal file
39
server/models/transaction/model.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package transaction
|
||||
|
||||
type SendTransactionRequest struct {
|
||||
FromAddress string `json:"from_address"`
|
||||
ToAddress string `json:"to_address"`
|
||||
Amount float64 `json:"amount"`
|
||||
Fee float64 `json:"fee"`
|
||||
Memo *string `json:"memo,omitempty"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type TransactionResponse struct {
|
||||
Hash string `json:"hash"`
|
||||
FromAddress *string `json:"from_address,omitempty"`
|
||||
ToAddress string `json:"to_address"`
|
||||
Amount float64 `json:"amount"`
|
||||
Fee float64 `json:"fee"`
|
||||
Memo *string `json:"memo,omitempty"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Status string `json:"status"`
|
||||
Confirmations int `json:"confirmations"`
|
||||
}
|
||||
|
||||
type TransactionHistoryResponse struct {
|
||||
Transactions []TransactionResponse `json:"transactions"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type TransactionStatus string
|
||||
|
||||
const (
|
||||
PENDING TransactionStatus = "PENDING"
|
||||
CONFIRMED TransactionStatus = "CONFIRMED"
|
||||
FAILED TransactionStatus = "FAILED"
|
||||
CANCELLED TransactionStatus = "CANCELLED"
|
||||
)
|
||||
35
server/models/wallet/model.go
Normal file
35
server/models/wallet/model.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package wallet
|
||||
|
||||
import "time"
|
||||
|
||||
type Wallet struct {
|
||||
Address string `db:"address" json:"address"`
|
||||
Label *string `db:"label" json:"label,omitempty"`
|
||||
PasswordHash string `db:"password_hash" json:"password_hash"`
|
||||
Balance float64 `db:"balance" json:"balance"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
LastActivity *time.Time `db:"last_activity" json:"last_activity,omitempty"`
|
||||
}
|
||||
|
||||
type CreateWalletRequest struct {
|
||||
Label *string `json:"label,omitempty"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type WalletResponse struct {
|
||||
Address string `json:"address"` // Format: "random_word:123456" (e.g. "cave:595462")
|
||||
Balance float64 `json:"balance"`
|
||||
Label *string `json:"label,omitempty"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
LastActivity *int64 `json:"last_activity,omitempty"`
|
||||
}
|
||||
|
||||
type BalanceResponse struct {
|
||||
Address string `json:"address"`
|
||||
Balance float64 `json:"balance"`
|
||||
}
|
||||
|
||||
type UpdateWalletRequest struct {
|
||||
Label *string `json:"label,omitempty"`
|
||||
}
|
||||
35
server/routes/wallet/routes.go
Normal file
35
server/routes/wallet/routes.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"ccoin/models/wallet"
|
||||
ws "ccoin/services/wallet"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Sets up routes for wallet operations
|
||||
func WalletRoutes(router *mux.Router, walletService *ws.WalletService) {
|
||||
walletRouter := router.PathPrefix("/wallet").Subrouter()
|
||||
|
||||
// Create a new wallet
|
||||
walletRouter.HandleFunc("/create", func(w http.ResponseWriter, r *http.Request) {
|
||||
var req wallet.CreateWalletRequest
|
||||
|
||||
// Parse the request
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Create the wallet using the service
|
||||
walletResponse, err := walletService.CreateWallet(req)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to create wallet: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(walletResponse)
|
||||
}).Methods("POST")
|
||||
}
|
||||
35
server/server.go
Normal file
35
server/server.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"ccoin/config/database"
|
||||
"ccoin/config/server"
|
||||
"ccoin/routes/wallet"
|
||||
ws "ccoin/services/wallet"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize server
|
||||
server.NewServerConfig()
|
||||
|
||||
// Initialize database
|
||||
dbConfig := database.NewDatabaseConfig()
|
||||
if err := dbConfig.Init(); err != nil {
|
||||
log.Fatal("Database initialization failed:", err)
|
||||
}
|
||||
|
||||
walletService := ws.NewWalletService(dbConfig.GetDB())
|
||||
|
||||
// Set up router
|
||||
r := mux.NewRouter()
|
||||
routes.WalletRoutes(r, walletService)
|
||||
|
||||
// Start server
|
||||
log.Info("Starting server on :8080")
|
||||
if err := http.ListenAndServe(":8080", r); err != nil {
|
||||
log.Fatal("Failed to start server:", err)
|
||||
}
|
||||
}
|
||||
|
||||
219
server/services/wallet/service.go
Normal file
219
server/services/wallet/service.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"ccoin/utils/crypto"
|
||||
"database/sql"
|
||||
"ccoin/models/wallet"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WalletService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// Creates a new instance of WalletService
|
||||
func NewWalletService(db *sql.DB) *WalletService {
|
||||
return &WalletService{db: db}
|
||||
}
|
||||
|
||||
// Creates a new wallet with optional label
|
||||
func (ws *WalletService) CreateWallet(req wallet.CreateWalletRequest) (wallet.WalletResponse, error) {
|
||||
address := crypto.GenerateWalletAddress()
|
||||
timestamp := time.Now()
|
||||
var passwordHash string
|
||||
if req.Password != "" {
|
||||
passwordHash = crypto.HashPassword(req.Password)
|
||||
} else {
|
||||
return wallet.WalletResponse{}, fmt.Errorf("password field is required")
|
||||
}
|
||||
|
||||
_, err := ws.db.Exec(`
|
||||
INSERT INTO wallets (address, balance, label, password_hash, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5)`,
|
||||
address, 0.0, req.Label, passwordHash, timestamp.Unix(),
|
||||
)
|
||||
if err != nil {
|
||||
return wallet.WalletResponse{}, err
|
||||
}
|
||||
|
||||
return wallet.WalletResponse{
|
||||
Address: address,
|
||||
Balance: 0.0,
|
||||
Label: req.Label,
|
||||
PasswordHash: passwordHash,
|
||||
CreatedAt: timestamp.Unix(),
|
||||
LastActivity: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Gets wallet by address
|
||||
func (ws *WalletService) GetWallet(address string) (*wallet.WalletResponse, error) {
|
||||
var w wallet.Wallet
|
||||
row := ws.db.QueryRow(`
|
||||
SELECT address, balance, label, password_hash, created_at, last_activity
|
||||
FROM wallets WHERE address = $1`, address)
|
||||
|
||||
err := row.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash, &w.CreatedAt, &w.LastActivity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wallet.WalletResponse{
|
||||
Address: w.Address,
|
||||
Balance: w.Balance,
|
||||
Label: w.Label,
|
||||
PasswordHash: w.PasswordHash,
|
||||
CreatedAt: w.CreatedAt.Unix(),
|
||||
LastActivity: ptrTime(w.LastActivity),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Gets wallet balance
|
||||
func (ws *WalletService) GetWalletBalance(address string) (wallet.BalanceResponse, error) {
|
||||
var balance float64
|
||||
err := ws.db.QueryRow(`SELECT balance FROM wallets WHERE address = $1`, address).Scan(&balance)
|
||||
if err != nil {
|
||||
return wallet.BalanceResponse{}, errors.New("wallet not found")
|
||||
}
|
||||
return wallet.BalanceResponse{
|
||||
Address: address,
|
||||
Balance: balance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Updates wallet balance
|
||||
func (ws *WalletService) UpdateBalance(address string, amount float64) (bool, error) {
|
||||
_, err := ws.db.Exec(`UPDATE wallets SET balance = balance + $1, last_activity = $2 WHERE address = $3`, amount, time.Now().Unix(), address)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Sets wallet balance to specific amount
|
||||
func (ws *WalletService) SetBalance(address string, amount float64) (bool, error) {
|
||||
_, err := ws.db.Exec(`UPDATE wallets SET balance = $1, last_activity = $2 WHERE address = $3`, amount, time.Now().Unix(), address)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Updates wallet label
|
||||
func (ws *WalletService) UpdateLabel(address string, req wallet.UpdateWalletRequest) (bool, error) {
|
||||
result, err := ws.db.Exec(`UPDATE wallets SET label = $1 WHERE address = $2`, address, req.Label)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
rowsAffected, _ := result.RowsAffected()
|
||||
return rowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// Checks if wallet exists
|
||||
func (ws *WalletService) WalletExists(address string) (bool, error) {
|
||||
var count int64
|
||||
err := ws.db.QueryRow(`SELECT COUNT(*) FROM wallets WHERE address = $1`, address).Scan(&count)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// Gets all wallets with pagination
|
||||
func (ws *WalletService) GetAllWallets(limit, offset int) ([]wallet.WalletResponse, error) {
|
||||
query := fmt.Sprintf(`
|
||||
SELECT address, balance, label, password_hash, created_at, last_activity
|
||||
FROM wallets ORDER BY created_at DESC LIMIT %d OFFSET %d`, limit, offset)
|
||||
|
||||
rows, err := ws.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var wallets []wallet.WalletResponse
|
||||
for rows.Next() {
|
||||
var w wallet.Wallet
|
||||
if err := rows.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash,
|
||||
&w.CreatedAt, &w.LastActivity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wallets = append(wallets, wallet.WalletResponse{
|
||||
Address: w.Address,
|
||||
Balance: w.Balance,
|
||||
Label: w.Label,
|
||||
PasswordHash: w.PasswordHash,
|
||||
CreatedAt: w.CreatedAt.Unix(),
|
||||
LastActivity: ptrTime(w.LastActivity),
|
||||
})
|
||||
}
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
// Gets total number of wallets
|
||||
func (ws *WalletService) GetTotalWalletCount() (int64, error) {
|
||||
var count int64
|
||||
err := ws.db.QueryRow(`SELECT COUNT(*) FROM wallets`).Scan(&count)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Gets wallet with balance greater than specified amount
|
||||
func (ws *WalletService) GetWalletsWithBalance(minBalance float64) ([]wallet.WalletResponse, error) {
|
||||
rows, err := ws.db.Query(`SELECT address, balance, label, password_hash, created_at, last_activity FROM wallets WHERE balance > $1 ORDER BY balance DESC`, minBalance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var wallets []wallet.WalletResponse
|
||||
for rows.Next() {
|
||||
var w wallet.Wallet
|
||||
if err := rows.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash, &w.CreatedAt, &w.LastActivity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wallets = append(wallets, wallet.WalletResponse{
|
||||
Address: w.Address,
|
||||
Balance: w.Balance,
|
||||
Label: w.Label,
|
||||
PasswordHash: w.PasswordHash,
|
||||
CreatedAt: w.CreatedAt.Unix(),
|
||||
LastActivity: ptrTime(w.LastActivity),
|
||||
})
|
||||
}
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
// Updates last activity timestamp
|
||||
func (ws *WalletService) UpdateLastActivity(address string) (bool, error) {
|
||||
_, err := ws.db.Exec(`UPDATE wallets SET last_activity = $1 WHERE address = $2`, time.Now().Unix(), address)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Gets total supply across all wallets
|
||||
func (ws *WalletService) GetTotalSupply() (float64, error) {
|
||||
var totalSupply float64
|
||||
err := ws.db.QueryRow(`SELECT SUM(balance) FROM wallets`).Scan(&totalSupply)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return totalSupply, nil
|
||||
}
|
||||
|
||||
// Helper function to convert *time.Time to *int64 for JSON response
|
||||
func ptrTime(t *time.Time) *int64 {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
unixTime := t.Unix()
|
||||
return &unixTime
|
||||
}
|
||||
131
server/utils/crypto/main.go
Normal file
131
server/utils/crypto/main.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"ccoin/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var words = []string{
|
||||
"phoenix", "dragon", "tiger", "eagle", "wolf", "lion", "bear", "shark",
|
||||
"falcon", "raven", "cobra", "viper", "panther", "jaguar", "leopard", "cheetah",
|
||||
"thunder", "lightning", "storm", "blizzard", "tornado", "hurricane", "cyclone", "tempest",
|
||||
"crystal", "diamond", "emerald", "ruby", "sapphire", "topaz", "amethyst", "opal",
|
||||
"shadow", "ghost", "phantom", "spirit", "wraith", "specter", "demon", "angel",
|
||||
"fire", "ice", "earth", "wind", "water", "metal", "wood", "void",
|
||||
"star", "moon", "sun", "comet", "meteor", "galaxy", "nebula", "cosmos",
|
||||
"blade", "sword", "arrow", "spear", "shield", "armor", "crown", "throne",
|
||||
"mountain", "ocean", "forest", "desert", "valley", "river", "lake", "cave",
|
||||
"knight", "warrior", "mage", "archer", "rogue", "paladin", "wizard", "sage",
|
||||
}
|
||||
|
||||
// Generates a wallet address in format: random_word:random_6_digits
|
||||
func GenerateWalletAddress() string {
|
||||
randomWord := words[utils.RandInt(0, len(words))]
|
||||
randomDigits := fmt.Sprintf("%06d", utils.RandInt(0, 1000000))
|
||||
return fmt.Sprintf("%s:%s", randomWord, randomDigits)
|
||||
}
|
||||
|
||||
// Validates if an address follows the correct format
|
||||
func IsValidAddress(address string) bool {
|
||||
parts := strings.Split(address, ":")
|
||||
if len(parts) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
word := parts[0]
|
||||
digits := parts[1]
|
||||
|
||||
return len(word) > 0 &&
|
||||
utils.IsAlpha(word) &&
|
||||
len(digits) == 6 &&
|
||||
utils.IsDigit(digits)
|
||||
}
|
||||
|
||||
// Generates SHA-256 hash of input string
|
||||
func SHA256(input string) string {
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(input))
|
||||
return fmt.Sprintf("%x", hash.Sum(nil))
|
||||
}
|
||||
|
||||
// Hashes password
|
||||
func HashPassword(password string) string {
|
||||
return SHA256(fmt.Sprintf("ccoin_password_%s", password))
|
||||
}
|
||||
|
||||
// Generates a transaction hash
|
||||
func GenerateTransactionHash(fromAddress *string, toAddress string, amount float64, timestamp int64, nonce int64) string {
|
||||
from := "genesis"
|
||||
if fromAddress != nil {
|
||||
from = *fromAddress
|
||||
}
|
||||
input := fmt.Sprintf("%s:%s:%f:%d:%d", from, toAddress, amount, timestamp, nonce)
|
||||
return SHA256(input)
|
||||
}
|
||||
|
||||
// Generates a block hash
|
||||
func GenerateBlockHash(previousHash string, merkleRoot string, timestamp int64, difficulty int, nonce int64) string {
|
||||
input := fmt.Sprintf("%s:%s:%d:%d:%d", previousHash, merkleRoot, timestamp, difficulty, nonce)
|
||||
return SHA256(input)
|
||||
}
|
||||
|
||||
// Validates if a hash meets the mining difficulty requirement
|
||||
func IsValidHash(hash string, difficulty int) bool {
|
||||
target := strings.Repeat("0", difficulty)
|
||||
return strings.HasPrefix(hash, target)
|
||||
}
|
||||
|
||||
// Generates mining job id
|
||||
func GenerateJobId() string {
|
||||
return SHA256(fmt.Sprintf("job:%d:%d", utils.GetCurrentTimeMillis(), utils.RandInt(0, 1<<63-1)))[:16]
|
||||
}
|
||||
|
||||
// Calculate merkle root from transaction hashes
|
||||
func CalculateMerkleRoot(transactionHashes []string) string {
|
||||
if len(transactionHashes) == 0 {
|
||||
return SHA256("empty")
|
||||
}
|
||||
|
||||
if len(transactionHashes) == 1 {
|
||||
return transactionHashes[0]
|
||||
}
|
||||
|
||||
hashes := transactionHashes
|
||||
|
||||
for len(hashes) > 1 {
|
||||
var newHashes []string
|
||||
for i := 0; i < len(hashes); i += 2 {
|
||||
left := hashes[i]
|
||||
right := left // Default to left if there's no right
|
||||
if i+1< len(hashes) {
|
||||
right = hashes[i+1]
|
||||
}
|
||||
newHashes = append(newHashes, SHA256(fmt.Sprintf("%s:%s", left, right)))
|
||||
}
|
||||
hashes = newHashes
|
||||
}
|
||||
|
||||
return hashes[0]
|
||||
}
|
||||
|
||||
// Generates a random nonce for mining
|
||||
func GenerateNonce() int64 {
|
||||
return int64(utils.RandInt(0, 1<<63-1))
|
||||
}
|
||||
|
||||
// Validates transaction hash format
|
||||
func IsValidTransactionHash(hash string) bool {
|
||||
return len(hash) == 64 && utils.IsHex(hash)
|
||||
}
|
||||
|
||||
// Validates block hash format
|
||||
func IsValidBlockHash(hash string) bool {
|
||||
return len(hash) == 64 && utils.IsHex(hash)
|
||||
}
|
||||
|
||||
// Verifies password hash
|
||||
func VerifyPassword(password string, storedHash string) bool {
|
||||
return HashPassword(password) == storedHash
|
||||
}
|
||||
33
server/utils/env.go
Normal file
33
server/utils/env.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Gets value from environment or uses default value
|
||||
func GetEnvOrDefault[T string | int | bool | float64](key string, defaultValue T) T {
|
||||
value, exists := os.LookupEnv(key)
|
||||
if !exists {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Handle conversion based on the type of defaultValue
|
||||
switch any(defaultValue).(type) {
|
||||
case string:
|
||||
return any(value).(T)
|
||||
case int:
|
||||
if intValue, err := strconv.Atoi(value); err == nil {
|
||||
return any(intValue).(T)
|
||||
}
|
||||
case bool:
|
||||
if boolValue, err := strconv.ParseBool(value); err == nil {
|
||||
return any(boolValue).(T)
|
||||
}
|
||||
case float64:
|
||||
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return any(floatValue).(T)
|
||||
}
|
||||
}
|
||||
return defaultValue // return default value if conversion fails
|
||||
}
|
||||
2
server/utils/extensions.go
Normal file
2
server/utils/extensions.go
Normal file
@@ -0,0 +1,2 @@
|
||||
package utils
|
||||
|
||||
48
server/utils/general.go
Normal file
48
server/utils/general.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Generates a random integer in the range (min, max)
|
||||
func RandInt(min, max int) int {
|
||||
n, _ := rand.Int(rand.Reader, big.NewInt(int64(max-min)))
|
||||
return int(n.Int64()) + min
|
||||
}
|
||||
|
||||
// Get the current time in milliseconds
|
||||
func GetCurrentTimeMillis() int64 {
|
||||
return time.Now().UnixNano() / int64(time.Millisecond) // Convert nanoseconds to milliseconds
|
||||
}
|
||||
|
||||
// IsAlpha checks if the string contains only alphabetic characters
|
||||
func IsAlpha(s string) bool {
|
||||
for _, r := range s {
|
||||
if !('a' <= r && r <= 'z') && !('A' <= r && r <= 'Z') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsDigit checks if the string contains only digits
|
||||
func IsDigit(s string) bool {
|
||||
for _, r := range s {
|
||||
if !(r >= '0' && r <= '9') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsHex checks if the string is a valid hexadecimal representation
|
||||
func IsHex(s string) bool {
|
||||
for _, r := range s {
|
||||
if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
2
server/utils/hash/main.go
Normal file
2
server/utils/hash/main.go
Normal file
@@ -0,0 +1,2 @@
|
||||
package hash
|
||||
|
||||
Reference in New Issue
Block a user