Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EthereumStratum/2.0.0 implementation to support both ProgPoW and ethash #442

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 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
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

### Features

**This pool is being further developed to provide an easy to use pool for Ethereum miners. This software is functional however an optimised release of the pool is expected soon. Testing and bug submissions are welcome!**
**This pool is no longer supported, expect only casual fixes.**

**Parity client is MANDATORY. Geth is no longer supported.**

* Support for HTTP and Stratum mining
* Detailed block stats with luck percentage and full reward
* Failover geth instances: geth high availability built in
* Parity nodes rpc failover built in
* Modern beautiful Ember.js frontend
* Separate stats for workers: can highlight timed-out workers so miners can perform maintenance of rigs
* JSON-API for stats
Expand All @@ -25,7 +27,7 @@
Dependencies:

* go >= 1.9
* geth or parity
* parity (will not work with geth)
* redis-server >= 2.8.0
* nodejs >= 4 LTS
* nginx
Expand Down Expand Up @@ -131,10 +133,13 @@ otherwise you will get errors on start because of JSON comments.**
// Bind stratum mining socket to this IP:PORT
"listen": "0.0.0.0:8008",
"timeout": "120s",
"maxConn": 8192
"maxConn": 8192,
"tls": false,
"certFile": "/path/to/cert.pem",
"keyFile": "/path/to/key.pem"
},

// Try to get new job from geth in this interval
// Try to get new job from node in this interval
"blockRefreshInterval": "120ms",
"stateUpdateInterval": "3s",
// Require this share difficulty from miners
Expand Down Expand Up @@ -208,10 +213,10 @@ otherwise you will get errors on start because of JSON comments.**
"purgeOnly": false
},

// Check health of each geth node in this interval
// Check health of each node in this interval
"upstreamCheckInterval": "5s",

/* List of geth nodes to poll for new jobs. Pool will try to get work from
/* List of parity nodes to poll for new jobs. Pool will try to get work from
first alive one and check in background for failed to back up.
Current block template of the pool is always cached in RAM indeed.
*/
Expand Down Expand Up @@ -254,9 +259,9 @@ otherwise you will get errors on start because of JSON comments.**
"keepTxFees": false,
// Run unlocker in this interval
"interval": "10m",
// Geth instance node rpc endpoint for unlocking blocks
// Parity node rpc endpoint for unlocking blocks
"daemon": "http://127.0.0.1:8545",
// Rise error if can't reach geth in this amount of time
// Rise error if can't reach parity
"timeout": "10s"
},

Expand All @@ -267,13 +272,13 @@ otherwise you will get errors on start because of JSON comments.**
"requirePeers": 25,
// Run payouts in this interval
"interval": "12h",
// Geth instance node rpc endpoint for payouts processing
// Parity node rpc endpoint for payouts processing
"daemon": "http://127.0.0.1:8545",
// Rise error if can't reach geth in this amount of time
// Rise error if can't reach parity
"timeout": "10s",
// Address with pool balance
"address": "0x0",
// Let geth to determine gas and gasPrice
// Let parity to determine gas and gasPrice
"autoGas": true,
// Gas amount and price for payout tx (advanced users only)
"gas": "21000",
Expand Down Expand Up @@ -303,10 +308,6 @@ I recommend this deployment strategy:
* Don't run payouts and unlocker modules as part of mining node. Create separate configs for both, launch independently and make sure you have a single instance of each module running.
* If `poolFeeAddress` is not specified all pool profit will remain on coinbase address. If it specified, make sure to periodically send some dust back required for payments.

### Alternative Ethereum Implementations

This pool is tested to work with [Ethcore's Parity](https://github.com/ethcore/parity). Mining and block unlocking works, but I am not sure about payouts and suggest to run *official* geth node for payments.

### Credits

Made by sammy007. Licensed under GPLv3.
Expand Down
6 changes: 5 additions & 1 deletion config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
"hashrateExpiration": "3h",

"healthCheck": true,
"debug": false,
"maxFails": 100,

"stratum": {
"enabled": true,
"listen": "0.0.0.0:8008",
"timeout": "120s",
"maxConn": 8192
"maxConn": 8192,
"tls": false,
"certFile": "/path/to/cert.pem",
"keyFile": "/path/to/key.pem"
},

"policy": {
Expand Down
4 changes: 2 additions & 2 deletions payouts/unlocker.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func (u *BlockUnlocker) unlockPendingBlocks() {
return
}

current, err := u.rpc.GetPendingBlock()
current, err := u.rpc.GetLatestBlock()
if err != nil {
u.halt = true
u.lastFail = err
Expand Down Expand Up @@ -351,7 +351,7 @@ func (u *BlockUnlocker) unlockAndCreditMiners() {
return
}

current, err := u.rpc.GetPendingBlock()
current, err := u.rpc.GetLatestBlock()
if err != nil {
u.halt = true
u.lastFail = err
Expand Down
71 changes: 39 additions & 32 deletions proxy/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,37 +47,37 @@ func (b Block) MixDigest() common.Hash { return b.mixDigest }
func (b Block) NumberU64() uint64 { return b.number }

func (s *ProxyServer) fetchBlockTemplate() {
rpc := s.rpc()
r := s.rpc()
t := s.currentBlockTemplate()
pendingReply, height, diff, err := s.fetchPendingBlock()
reply, err := r.GetWork()
if err != nil {
log.Printf("Error while refreshing pending block on %s: %s", rpc.Name, err)
return
}
reply, err := rpc.GetWork()
if err != nil {
log.Printf("Error while refreshing block template on %s: %s", rpc.Name, err)
log.Printf("Error while refreshing block template on %s: %s", r.Name, err)
return
}
// No need to update, we have fresh job
if t != nil && t.Header == reply[0] {
return
}
diff := util.TargetHexToDiff(reply[2])
height, err := strconv.ParseUint(strings.Replace(reply[3], "0x", "", -1), 16, 64)

pendingReply.Difficulty = util.ToHex(s.config.Proxy.Difficulty)
pendingReply := &rpc.GetBlockReplyPart{
Difficulty: util.ToHex(s.config.Proxy.Difficulty),
Number: reply[3],
}

newTemplate := BlockTemplate{
Header: reply[0],
Seed: reply[1],
Target: reply[2],
Height: height,
Difficulty: big.NewInt(diff),
Difficulty: diff,
GetPendingBlockCache: pendingReply,
headers: make(map[string]heightDiffPair),
}
// Copy job backlog and add current one
newTemplate.headers[reply[0]] = heightDiffPair{
diff: util.TargetHexToDiff(reply[2]),
diff: diff,
height: height,
}
if t != nil {
Expand All @@ -88,30 +88,37 @@ func (s *ProxyServer) fetchBlockTemplate() {
}
}
s.blockTemplate.Store(&newTemplate)
log.Printf("New block to mine on %s at height %d / %s", rpc.Name, height, reply[0][0:10])

// check forkBlocks
if len(s.config.Proxy.ForkBlock) > 0 {
algo := s.algorithm
forkindex := -1
for i, block := range s.config.Proxy.ForkBlock {
tmp := block.Algorithm
if tmp != algo && height >= block.Block {
algo = tmp
forkindex = i
}
}
s.config.Proxy.ForkBlock = s.config.Proxy.ForkBlock[forkindex+1:]

if algo != s.algorithm {
log.Printf("Algorithm is changed at height %d from %v to %v", height, s.algorithm, algo)
}
// check validity
switch algo {
case "progpow":
s.algorithm = algo
break
default:
s.algorithm = "ethash"
break
}
}
log.Printf("New block to mine on %s at height %d / %s / %d", r.Name, height, reply[0][0:10], diff)

// Stratum
if s.config.Proxy.Stratum.Enabled {
go s.broadcastNewJobs()
}
}

func (s *ProxyServer) fetchPendingBlock() (*rpc.GetBlockReplyPart, uint64, int64, error) {
rpc := s.rpc()
reply, err := rpc.GetPendingBlock()
if err != nil {
log.Printf("Error while refreshing pending block on %s: %s", rpc.Name, err)
return nil, 0, 0, err
}
blockNumber, err := strconv.ParseUint(strings.Replace(reply.Number, "0x", "", -1), 16, 64)
if err != nil {
log.Println("Can't parse pending block number")
return nil, 0, 0, err
}
blockDiff, err := strconv.ParseInt(strings.Replace(reply.Difficulty, "0x", "", -1), 16, 64)
if err != nil {
log.Println("Can't parse pending block difficulty")
return nil, 0, 0, err
}
return reply, blockNumber, blockDiff, nil
}
20 changes: 16 additions & 4 deletions proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,32 @@ type Proxy struct {
Difficulty int64 `json:"difficulty"`
StateUpdateInterval string `json:"stateUpdateInterval"`
HashrateExpiration string `json:"hashrateExpiration"`
Algorithm string `json:"algorithm"`

ForkBlock []ForkBlock `json:"forkBlock"`

Policy policy.Config `json:"policy"`

MaxFails int64 `json:"maxFails"`
HealthCheck bool `json:"healthCheck"`
Debug bool `json:"debug"`

Stratum Stratum `json:"stratum"`
}

type Stratum struct {
Enabled bool `json:"enabled"`
Listen string `json:"listen"`
Timeout string `json:"timeout"`
MaxConn int `json:"maxConn"`
Enabled bool `json:"enabled"`
Listen string `json:"listen"`
Timeout string `json:"timeout"`
MaxConn int `json:"maxConn"`
TLS bool `json:"tls"`
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
}

type ForkBlock struct {
Block uint64 `json:"block"`
Algorithm string `json:"algorithm"`
}

type Upstream struct {
Expand Down
23 changes: 19 additions & 4 deletions proxy/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (s *ProxyServer) handleGetWorkRPC(cs *Session) ([]string, *ErrorReply) {
if t == nil || len(t.Header) == 0 || s.isSick() {
return nil, &ErrorReply{Code: 0, Message: "Work not ready"}
}
return []string{t.Header, t.Seed, s.diff}, nil
return []string{t.Header, t.Seed, s.diff, util.ToHex(int64(t.Height))}, nil
}

// Stratum
Expand All @@ -63,18 +63,31 @@ func (s *ProxyServer) handleSubmitRPC(cs *Session, login, id string, params []st
return false, &ErrorReply{Code: -1, Message: "Invalid params"}
}

stratumMode := cs.stratumMode()
if stratumMode != EthProxy {
for i := 0; i <= 2; i++ {
if params[i][0:2] != "0x" {
params[i] = "0x" + params[i]
}
}
}

if !noncePattern.MatchString(params[0]) || !hashPattern.MatchString(params[1]) || !hashPattern.MatchString(params[2]) {
s.policy.ApplyMalformedPolicy(cs.ip)
log.Printf("Malformed PoW result from %s@%s %v", login, cs.ip, params)
return false, &ErrorReply{Code: -1, Message: "Malformed PoW result"}
}
t := s.currentBlockTemplate()
exist, validShare := s.processShare(login, id, cs.ip, t, params)
exist, validShare := s.processShare(login, id, cs.ip, t, params, cs.algorithm, stratumMode != EthProxy)
ok := s.policy.ApplySharePolicy(cs.ip, !exist && validShare)

if exist {
log.Printf("Duplicate share from %s@%s %v", login, cs.ip, params)
return false, &ErrorReply{Code: 22, Message: "Duplicate share"}
// see https://github.com/sammy007/open-ethereum-pool/compare/master...nicehashdev:patch-1
if !ok {
return false, &ErrorReply{Code: 23, Message: "Invalid share"}
}
return false, nil
}

if !validShare {
Expand All @@ -85,7 +98,9 @@ func (s *ProxyServer) handleSubmitRPC(cs *Session, login, id string, params []st
}
return false, nil
}
log.Printf("Valid share from %s@%s", login, cs.ip)
if s.config.Proxy.Debug {
log.Printf("Valid share from %s@%s", login, cs.ip)
}

if !ok {
return true, &ErrorReply{Code: -1, Message: "High rate of invalid shares"}
Expand Down
Loading