246 lines
7.5 KiB
Go
246 lines
7.5 KiB
Go
package mining
|
|
|
|
import (
|
|
"net/http"
|
|
)
|
|
|
|
// respondWithError(requestContext, http.StatusNotFound, ErrCodeMinerNotFound, "xmrig not found", err.Error())
|
|
// respondWithError(requestContext, http.StatusConflict, ErrCodeMinerExists, "xmrig already running", "")
|
|
const (
|
|
ErrCodeMinerNotFound = "MINER_NOT_FOUND"
|
|
ErrCodeMinerExists = "MINER_EXISTS"
|
|
ErrCodeMinerNotRunning = "MINER_NOT_RUNNING"
|
|
ErrCodeInstallFailed = "INSTALL_FAILED"
|
|
ErrCodeStartFailed = "START_FAILED"
|
|
ErrCodeStopFailed = "STOP_FAILED"
|
|
ErrCodeInvalidConfig = "INVALID_CONFIG"
|
|
ErrCodeInvalidInput = "INVALID_INPUT"
|
|
ErrCodeUnsupportedMiner = "UNSUPPORTED_MINER"
|
|
ErrCodeNotSupported = "NOT_SUPPORTED"
|
|
ErrCodeConnectionFailed = "CONNECTION_FAILED"
|
|
ErrCodeServiceUnavailable = "SERVICE_UNAVAILABLE"
|
|
ErrCodeTimeout = "TIMEOUT"
|
|
ErrCodeDatabaseError = "DATABASE_ERROR"
|
|
ErrCodeProfileNotFound = "PROFILE_NOT_FOUND"
|
|
ErrCodeProfileExists = "PROFILE_EXISTS"
|
|
ErrCodeInternalError = "INTERNAL_ERROR"
|
|
)
|
|
|
|
// miningError := &MiningError{Code: ErrCodeStartFailed, Message: "failed to start xmrig", HTTPStatus: 500}
|
|
// miningError.WithCause(err).WithSuggestion("check logs")
|
|
type MiningError struct {
|
|
Code string
|
|
Message string
|
|
Details string
|
|
Suggestion string
|
|
Retryable bool
|
|
HTTPStatus int
|
|
Cause error
|
|
}
|
|
|
|
// e.Error() // "START_FAILED: failed to start miner 'xmrig' (exec: not found)"
|
|
func (e *MiningError) Error() string {
|
|
if e.Cause != nil {
|
|
return e.Code + ": " + e.Message + " (" + e.Cause.Error() + ")"
|
|
}
|
|
return e.Code + ": " + e.Message
|
|
}
|
|
|
|
// errors.As(e.Unwrap(), &target) // unwrap to inspect the underlying cause
|
|
func (e *MiningError) Unwrap() error {
|
|
return e.Cause
|
|
}
|
|
|
|
// return ErrStartFailed("xmrig").WithCause(err)
|
|
func (e *MiningError) WithCause(err error) *MiningError {
|
|
e.Cause = err
|
|
return e
|
|
}
|
|
|
|
// return ErrInternal("nil response").WithDetails("stats API returned null body")
|
|
func (e *MiningError) WithDetails(details string) *MiningError {
|
|
e.Details = details
|
|
return e
|
|
}
|
|
|
|
// return ErrInstallFailed("xmrig").WithSuggestion("ensure curl is installed")
|
|
func (e *MiningError) WithSuggestion(suggestion string) *MiningError {
|
|
e.Suggestion = suggestion
|
|
return e
|
|
}
|
|
|
|
// if e.IsRetryable() { time.Sleep(backoff); retry() }
|
|
func (e *MiningError) IsRetryable() bool {
|
|
return e.Retryable
|
|
}
|
|
|
|
// requestContext.JSON(e.StatusCode(), e) // 404 for not-found, 500 for internal errors
|
|
func (e *MiningError) StatusCode() int {
|
|
if e.HTTPStatus == 0 {
|
|
return http.StatusInternalServerError
|
|
}
|
|
return e.HTTPStatus
|
|
}
|
|
|
|
// NewMiningError("START_FAILED", "failed to start xmrig").WithCause(err)
|
|
func NewMiningError(code, message string) *MiningError {
|
|
return &MiningError{
|
|
Code: code,
|
|
Message: message,
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
}
|
|
}
|
|
|
|
// return ErrMinerNotFound("xmrig").WithCause(err)
|
|
func ErrMinerNotFound(name string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeMinerNotFound,
|
|
Message: "miner '" + name + "' not found",
|
|
Suggestion: "Check that the miner name is correct and that it is running",
|
|
Retryable: false,
|
|
HTTPStatus: http.StatusNotFound,
|
|
}
|
|
}
|
|
|
|
// return ErrMinerExists("xmrig")
|
|
func ErrMinerExists(name string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeMinerExists,
|
|
Message: "miner '" + name + "' is already running",
|
|
Suggestion: "Stop the existing miner first or use a different configuration",
|
|
Retryable: false,
|
|
HTTPStatus: http.StatusConflict,
|
|
}
|
|
}
|
|
|
|
// return ErrMinerNotRunning("xmrig")
|
|
func ErrMinerNotRunning(name string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeMinerNotRunning,
|
|
Message: "miner '" + name + "' is not running",
|
|
Suggestion: "Start the miner first before performing this operation",
|
|
Retryable: false,
|
|
HTTPStatus: http.StatusBadRequest,
|
|
}
|
|
}
|
|
|
|
// return ErrInstallFailed("xmrig").WithCause(err)
|
|
func ErrInstallFailed(minerType string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeInstallFailed,
|
|
Message: "failed to install " + minerType,
|
|
Suggestion: "Check your internet connection and try again",
|
|
Retryable: true,
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
}
|
|
}
|
|
|
|
// return ErrStartFailed("xmrig").WithCause(err)
|
|
func ErrStartFailed(name string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeStartFailed,
|
|
Message: "failed to start miner '" + name + "'",
|
|
Suggestion: "Check the miner configuration and logs for details",
|
|
Retryable: true,
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
}
|
|
}
|
|
|
|
// return ErrStopFailed("xmrig").WithCause(err)
|
|
func ErrStopFailed(name string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeStopFailed,
|
|
Message: "failed to stop miner '" + name + "'",
|
|
Suggestion: "The miner process may need to be terminated manually",
|
|
Retryable: true,
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
}
|
|
}
|
|
|
|
// return ErrInvalidConfig("wallet address is required")
|
|
func ErrInvalidConfig(reason string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeInvalidConfig,
|
|
Message: "invalid configuration: " + reason,
|
|
Suggestion: "Review the configuration and ensure all required fields are provided",
|
|
Retryable: false,
|
|
HTTPStatus: http.StatusBadRequest,
|
|
}
|
|
}
|
|
|
|
// return ErrUnsupportedMiner("nicehash")
|
|
func ErrUnsupportedMiner(minerType string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeUnsupportedMiner,
|
|
Message: "unsupported miner type: " + minerType,
|
|
Suggestion: "Use one of the supported miner types: xmrig, tt-miner",
|
|
Retryable: false,
|
|
HTTPStatus: http.StatusBadRequest,
|
|
}
|
|
}
|
|
|
|
// return ErrConnectionFailed("pool.minexmr.com:4444").WithCause(err)
|
|
func ErrConnectionFailed(target string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeConnectionFailed,
|
|
Message: "failed to connect to " + target,
|
|
Suggestion: "Check network connectivity and try again",
|
|
Retryable: true,
|
|
HTTPStatus: http.StatusServiceUnavailable,
|
|
}
|
|
}
|
|
|
|
// return ErrTimeout("stats poll")
|
|
func ErrTimeout(operation string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeTimeout,
|
|
Message: "operation timed out: " + operation,
|
|
Suggestion: "The operation is taking longer than expected, try again later",
|
|
Retryable: true,
|
|
HTTPStatus: http.StatusGatewayTimeout,
|
|
}
|
|
}
|
|
|
|
// return ErrDatabaseError("save profile")
|
|
func ErrDatabaseError(operation string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeDatabaseError,
|
|
Message: "database error during " + operation,
|
|
Suggestion: "This may be a temporary issue, try again",
|
|
Retryable: true,
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
}
|
|
}
|
|
|
|
// return ErrProfileNotFound("default-xmr")
|
|
func ErrProfileNotFound(id string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeProfileNotFound,
|
|
Message: "profile '" + id + "' not found",
|
|
Suggestion: "Check that the profile ID is correct",
|
|
Retryable: false,
|
|
HTTPStatus: http.StatusNotFound,
|
|
}
|
|
}
|
|
|
|
// return ErrProfileExists("default-xmr")
|
|
func ErrProfileExists(name string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeProfileExists,
|
|
Message: "profile '" + name + "' already exists",
|
|
Suggestion: "Use a different name or update the existing profile",
|
|
Retryable: false,
|
|
HTTPStatus: http.StatusConflict,
|
|
}
|
|
}
|
|
|
|
// return ErrInternal("unexpected nil response from stats API")
|
|
func ErrInternal(message string) *MiningError {
|
|
return &MiningError{
|
|
Code: ErrCodeInternalError,
|
|
Message: message,
|
|
Suggestion: "Please report this issue if it persists",
|
|
Retryable: true,
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
}
|
|
}
|