diff --git a/.gitignore b/.gitignore index 3a4cf3c..8b15722 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules package-lock.json .idea go.sum +miner diff --git a/cmd/mine/main.go b/cmd/mine/main.go new file mode 100644 index 0000000..69da0cf --- /dev/null +++ b/cmd/mine/main.go @@ -0,0 +1,184 @@ +package main + +import ( + "fmt" + "github.com/Snider/Enchantrix/pkg/config" + "github.com/Snider/Enchantrix/pkg/miner" + "github.com/Snider/Enchantrix/pkg/pool" + "github.com/Snider/Enchantrix/pkg/proxy" + "github.com/gin-gonic/gin" + "github.com/leaanthony/clir" + "github.com/sirupsen/logrus" + "net/http" + "strconv" +) + +func main() { + // Create a new cli application + cli := clir.NewCli("Enchantrix Miner", "A miner for the Enchantrix project", "v0.0.1") + + // Create a new config + cfg := config.New() + + // Create a start command + startCmd := cli.NewSubCommand("start", "Starts the miner") + + // Define flags + var configFile string + startCmd.StringFlag("config", "Path to config file", &configFile) + + var logLevel string + startCmd.StringFlag("log-level", "Log level (trace, debug, info, warn, error, fatal, panic)", &logLevel) + + var url string + startCmd.StringFlag("url", "URL of mining pool", &url) + + var user string + startCmd.StringFlag("user", "Username for mining pool", &user) + + var pass string + startCmd.StringFlag("pass", "Password for mining pool", &pass) + + var numThreads int + startCmd.IntFlag("threads", "Number of miner threads", &numThreads) + + + startCmd.Action(func() error { + // Set up logging + level, err := logrus.ParseLevel(logLevel) + if err != nil { + level = logrus.InfoLevel + } + logrus.SetLevel(level) + + // Load config from file if specified + if configFile != "" { + if err := cfg.Load(configFile); err != nil { + return err + } + } + + logrus.Info("Starting the miner...") + + // Override config with flags + if url != "" { + cfg.Pools = []struct { + URL string `json:"url"` + User string `json:"user"` + Pass string `json:"pass"` + }{{URL: url, User: user, Pass: pass}} + } + if numThreads == 0 { + numThreads = 1 + } + + + // Create a new miner + algo := &miner.MockAlgo{} + m := miner.New(algo, cfg.Pools[0].URL, cfg.Pools[0].User, cfg.Pools[0].Pass, numThreads) + m.Start() + defer m.Stop() + + // Create a new pool client + p := pool.New(cfg.Pools[0].URL, cfg.Pools[0].User, cfg.Pools[0].Pass, m.JobQueue) + p.Start() + defer p.Stop() + + + if cfg.Pools[0].URL != "" { + logrus.Infof("Connecting to %s as %s", cfg.Pools[0].URL, cfg.Pools[0].User) + } + + // Set up the Gin router + router := gin.Default() + router.GET("/1/miners", func(c *gin.Context) { + c.JSON(http.StatusOK, []gin.H{ + { + "id": 0, + "status": "running", + "summary": m.StateManager.Summary(), + }, + }) + }) + router.GET("/1/miner/:id/status", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil || id != 0 { + c.JSON(http.StatusNotFound, gin.H{"error": "miner not found"}) + return + } + c.JSON(http.StatusOK, m.StateManager.Summary()) + }) + router.GET("/1/config", func(c *gin.Context) { + c.JSON(http.StatusOK, cfg.Get()) + }) + router.PUT("/1/config", func(c *gin.Context) { + var newConfig config.Config + if err := c.ShouldBindJSON(&newConfig); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + cfg.Update(&newConfig) + c.JSON(http.StatusOK, cfg.Get()) + }) + router.GET("/1/threads", func(c *gin.Context) { + c.JSON(http.StatusOK, m.StateManager.ThreadsSummary()) + }) + + // Start the server + logrus.Infof("Starting API server on http://%s:%d", cfg.HTTP.Host, cfg.HTTP.Port) + return router.Run(fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port)) + }) + + // Create a proxy command + proxyCmd := cli.NewSubCommand("proxy", "Starts the proxy") + + // Define flags + var proxyConfigFile string + proxyCmd.StringFlag("config", "Path to config file", &proxyConfigFile) + + var proxyLogLevel string + proxyCmd.StringFlag("log-level", "Log level (trace, debug, info, warn, error, fatal, panic)", &proxyLogLevel) + + proxyCmd.Action(func() error { + // Set up logging + level, err := logrus.ParseLevel(proxyLogLevel) + if err != nil { + level = logrus.InfoLevel + } + logrus.SetLevel(level) + + // Load config from file if specified + if proxyConfigFile != "" { + if err := cfg.Load(proxyConfigFile); err != nil { + return err + } + } + + logrus.Info("Starting the proxy...") + + // Create a new proxy + p := proxy.New() + p.Start() + defer p.Stop() + + // Set up the Gin router + router := gin.Default() + router.GET("/", func(c *gin.Context) { + c.JSON(http.StatusOK, p.Summary()) + }) + router.GET("/workers.json", func(c *gin.Context) { + c.JSON(http.StatusOK, p.WorkersSummary()) + }) + + + // Start the server + logrus.Infof("Starting API server on http://%s:%d", cfg.HTTP.Host, cfg.HTTP.Port) + return router.Run(fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port)) + }) + + + // Run the cli + if err := cli.Run(); err != nil { + logrus.Fatal(err) + } +} diff --git a/config.json b/config.json new file mode 100644 index 0000000..efb6747 --- /dev/null +++ b/config.json @@ -0,0 +1,20 @@ +{ + "api": { + "id": "enchantrix-from-file", + "worker-id": "worker-1" + }, + "http": { + "enabled": true, + "host": "127.0.0.1", + "port": 8081, + "access-token": null, + "restricted": true + }, + "pools": [ + { + "url": "pool.example.com:3333", + "user": "testuser", + "pass": "testpass" + } + ] +} diff --git a/go.mod b/go.mod index 240d180..6fb4cfe 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,45 @@ module github.com/Snider/Enchantrix go 1.25 require ( + github.com/gin-gonic/gin v1.11.0 + github.com/leaanthony/clir v1.7.0 github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.43.0 ) require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.37.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index cd3b418..b24a58f 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,95 @@ +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leaanthony/clir v1.7.0 h1:xiAnhl7ryPwuH3ERwPWZp/pCHk8wTeiwuAOt6MiNyAw= +github.com/leaanthony/clir v1.7.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..330ca99 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,70 @@ +package config + +import ( + "encoding/json" + "io/ioutil" + "os" + "sync" +) + +type Config struct { + mu sync.RWMutex + API struct { + ID string `json:"id"` + WorkerID string `json:"worker-id"` + } `json:"api"` + HTTP struct { + Enabled bool `json:"enabled"` + Host string `json:"host"` + Port int `json:"port"` + AccessToken string `json:"access-token"` + Restricted bool `json:"restricted"` + } `json:"http"` + Pools []struct { + URL string `json:"url"` + User string `json:"user"` + Pass string `json:"pass"` + } `json:"pools"` +} + +func New() *Config { + cfg := &Config{} + if _, err := os.Stat("config.json"); err == nil { + cfg.Load("config.json") + } else { + cfg.API.ID = "enchantrix" + cfg.HTTP.Enabled = true + cfg.HTTP.Host = "127.0.0.1" + cfg.HTTP.Port = 8080 + cfg.HTTP.Restricted = true + } + return cfg +} + +func (c *Config) Load(path string) error { + c.mu.Lock() + defer c.mu.Unlock() + + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + return json.Unmarshal(data, c) +} + +func (c *Config) Get() *Config { + c.mu.RLock() + defer c.mu.RUnlock() + // To avoid returning a pointer to the internal struct, we create a copy + clone := *c + return &clone +} + +func (c *Config) Update(newConfig *Config) { + c.mu.Lock() + defer c.mu.Unlock() + c.API = newConfig.API + c.HTTP = newConfig.HTTP + c.Pools = newConfig.Pools +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 0000000..7f9d8fe --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,19 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig(t *testing.T) { + cfg := New() + assert.NotNil(t, cfg) + assert.Equal(t, 8080, cfg.HTTP.Port) + + newConfig := New() + newConfig.HTTP.Port = 8081 + cfg.Update(newConfig) + + assert.Equal(t, 8081, cfg.Get().HTTP.Port) +} diff --git a/pkg/pool/pool.go b/pkg/pool/pool.go new file mode 100644 index 0000000..dacee59 --- /dev/null +++ b/pkg/pool/pool.go @@ -0,0 +1,44 @@ +package pool + +import ( + "github.com/Snider/Enchantrix/pkg/miner" + "time" +) + +type PoolClient struct { + URL string + User string + Pass string + JobQueue *miner.JobQueue + stopChannel chan struct{} +} + +func New(url, user, pass string, jobQueue *miner.JobQueue) *PoolClient { + return &PoolClient{ + URL: url, + User: user, + Pass: pass, + JobQueue: jobQueue, + stopChannel: make(chan struct{}), + } +} + +func (p *PoolClient) Start() { + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + p.JobQueue.Set(miner.NewMockJob()) + case <-p.stopChannel: + return + } + } + }() +} + +func (p *PoolClient) Stop() { + close(p.stopChannel) +} diff --git a/pkg/pool/pool_test.go b/pkg/pool/pool_test.go new file mode 100644 index 0000000..e81c023 --- /dev/null +++ b/pkg/pool/pool_test.go @@ -0,0 +1,19 @@ +package pool + +import ( + "github.com/Snider/Enchantrix/pkg/miner" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestPoolClient(t *testing.T) { + jq := miner.NewJobQueue() + pc := New("test-url", "test-user", "test-pass", jq) + pc.Start() + time.Sleep(6 * time.Second) + pc.Stop() + + assert.NotNil(t, jq.Get()) +} diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go new file mode 100644 index 0000000..99f823e --- /dev/null +++ b/pkg/proxy/proxy.go @@ -0,0 +1,97 @@ +package proxy + +import ( + "fmt" + "math/rand" + "sync" + "time" +) + +type Proxy struct { + mu sync.RWMutex + StartTime time.Time + Workers []*Worker + stopChannel chan struct{} +} + +type Worker struct { + ID string + Hashrate float64 + Shares int + Connected time.Time +} + +func New() *Proxy { + return &Proxy{ + StartTime: time.Now(), + stopChannel: make(chan struct{}), + } +} + +func (p *Proxy) Start() { + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + p.mu.Lock() + // Simulate a worker connecting + if len(p.Workers) < 10 { + p.Workers = append(p.Workers, &Worker{ + ID: fmt.Sprintf("worker-%d", len(p.Workers)), + Hashrate: 100 + rand.Float64()*10-5, + Connected: time.Now(), + }) + } + p.mu.Unlock() + case <-p.stopChannel: + return + } + } + }() +} + +func (p *Proxy) Stop() { + close(p.stopChannel) +} + +func (p *Proxy) Summary() map[string]interface{} { + p.mu.RLock() + defer p.mu.RUnlock() + + var totalHashrate float64 + for _, worker := range p.Workers { + totalHashrate += worker.Hashrate + } + + return map[string]interface{}{ + "id": "enchantrix-proxy", + "version": "0.0.1", + "kind": "proxy", + "uptime": int64(time.Since(p.StartTime).Seconds()), + "hashrate": map[string]interface{}{ + "total": []float64{totalHashrate, totalHashrate, totalHashrate}, + }, + "miners": map[string]interface{}{ + "now": len(p.Workers), + "max": 10, + }, + } +} + +func (p *Proxy) WorkersSummary() []map[string]interface{} { + p.mu.RLock() + defer p.mu.RUnlock() + + summary := make([]map[string]interface{}, len(p.Workers)) + for i, worker := range p.Workers { + summary[i] = map[string]interface{}{ + "id": worker.ID, + "hashrate": worker.Hashrate, + "shares": worker.Shares, + } + } + return summary +} diff --git a/pkg/proxy/proxy_test.go b/pkg/proxy/proxy_test.go new file mode 100644 index 0000000..600cfe8 --- /dev/null +++ b/pkg/proxy/proxy_test.go @@ -0,0 +1,22 @@ +package proxy + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestProxy(t *testing.T) { + proxy := New() + proxy.Start() + time.Sleep(6 * time.Second) + proxy.Stop() + + summary := proxy.Summary() + assert.NotNil(t, summary) + + workers := proxy.WorkersSummary() + assert.NotNil(t, workers) + assert.True(t, len(workers) > 0) +}