@ -18,6 +18,7 @@ package ethash
import (
"bytes"
"context"
crand "crypto/rand"
"encoding/json"
"errors"
@ -33,7 +34,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
const (
@ -56,7 +56,7 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, resu
select {
case results <- block . WithSeal ( header ) :
default :
l og. Warn ( "Sealing result is not read by miner" , "mode" , "fake" , "sealhash" , ethash . SealHash ( block . Header ( ) ) )
ethash . config . L og. Warn ( "Sealing result is not read by miner" , "mode" , "fake" , "sealhash" , ethash . SealHash ( block . Header ( ) ) )
}
return nil
}
@ -85,8 +85,8 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, resu
threads = 0 // Allows disabling local mining without extra logic around local/remote
}
// Push new work to remote sealer
if ethash . workCh != nil {
ethash . workCh <- & sealTask { block : block , results : results }
if ethash . remote != nil {
ethash . remote . workCh <- & sealTask { block : block , results : results }
}
var (
pend sync . WaitGroup
@ -111,14 +111,14 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, resu
select {
case results <- result :
default :
l og. Warn ( "Sealing result is not read by miner" , "mode" , "local" , "sealhash" , ethash . SealHash ( block . Header ( ) ) )
ethash . config . L og. Warn ( "Sealing result is not read by miner" , "mode" , "local" , "sealhash" , ethash . SealHash ( block . Header ( ) ) )
}
close ( abort )
case <- ethash . update :
// Thread count was changed on user request, restart
close ( abort )
if err := ethash . Seal ( chain , block , results , stop ) ; err != nil {
l og. Error ( "Failed to restart sealing after update" , "err" , err )
ethash . config . L og. Error ( "Failed to restart sealing after update" , "err" , err )
}
}
// Wait for all miners to terminate and return the block
@ -143,7 +143,7 @@ func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan s
attempts = int64 ( 0 )
nonce = seed
)
logger := l og. New ( "miner" , id )
logger := ethash . config . L og. New ( "miner" , id )
logger . Trace ( "Started ethash search for new nonces" , "seed" , seed )
search :
for {
@ -186,160 +186,128 @@ search:
runtime . KeepAlive ( dataset )
}
// remote is a standalone goroutine to handle remote mining related stuff.
func ( ethash * Ethash ) remote ( notify [ ] string , noverify bool ) {
var (
works = make ( map [ common . Hash ] * types . Block )
rates = make ( map [ common . Hash ] hashrate )
// This is the timeout for HTTP requests to notify external miners.
const remoteSealerTimeout = 1 * time . Second
results chan <- * types . Block
currentBlock * types . Block
currentWork [ 4 ] string
type remoteSealer struct {
works map [ common . Hash ] * types . Block
rates map [ common . Hash ] hashrate
currentBlock * types . Block
currentWork [ 4 ] string
notifyCtx context . Context
cancelNotify context . CancelFunc // cancels all notification requests
reqWG sync . WaitGroup // tracks notification request goroutines
notifyTransport = & http . Transport { }
notifyClient = & http . Client {
Transport : notifyTransport ,
Timeout : time . Second ,
}
notifyReqs = make ( [ ] * http . Request , len ( notify ) )
)
// notifyWork notifies all the specified mining endpoints of the availability of
// new work to be processed.
notifyWork := func ( ) {
work := currentWork
blob , _ := json . Marshal ( work )
for i , url := range notify {
// Terminate any previously pending request and create the new work
if notifyReqs [ i ] != nil {
notifyTransport . CancelRequest ( notifyReqs [ i ] )
}
notifyReqs [ i ] , _ = http . NewRequest ( "POST" , url , bytes . NewReader ( blob ) )
notifyReqs [ i ] . Header . Set ( "Content-Type" , "application/json" )
// Push the new work concurrently to all the remote nodes
go func ( req * http . Request , url string ) {
res , err := notifyClient . Do ( req )
if err != nil {
log . Warn ( "Failed to notify remote miner" , "err" , err )
} else {
log . Trace ( "Notified remote miner" , "miner" , url , "hash" , log . Lazy { Fn : func ( ) common . Hash { return common . HexToHash ( work [ 0 ] ) } } , "target" , work [ 2 ] )
res . Body . Close ( )
}
} ( notifyReqs [ i ] , url )
}
}
// makeWork creates a work package for external miner.
//
// The work package consists of 3 strings:
// result[0], 32 bytes hex encoded current block header pow-hash
// result[1], 32 bytes hex encoded seed hash used for DAG
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
// result[3], hex encoded block number
makeWork := func ( block * types . Block ) {
hash := ethash . SealHash ( block . Header ( ) )
currentWork [ 0 ] = hash . Hex ( )
currentWork [ 1 ] = common . BytesToHash ( SeedHash ( block . NumberU64 ( ) ) ) . Hex ( )
currentWork [ 2 ] = common . BytesToHash ( new ( big . Int ) . Div ( two256 , block . Difficulty ( ) ) . Bytes ( ) ) . Hex ( )
currentWork [ 3 ] = hexutil . EncodeBig ( block . Number ( ) )
// Trace the seal work fetched by remote sealer.
currentBlock = block
works [ hash ] = block
}
// submitWork verifies the submitted pow solution, returning
// whether the solution was accepted or not (not can be both a bad pow as well as
// any other error, like no pending work or stale mining result).
submitWork := func ( nonce types . BlockNonce , mixDigest common . Hash , sealhash common . Hash ) bool {
if currentBlock == nil {
log . Error ( "Pending work without block" , "sealhash" , sealhash )
return false
}
// Make sure the work submitted is present
block := works [ sealhash ]
if block == nil {
log . Warn ( "Work submitted but none pending" , "sealhash" , sealhash , "curnumber" , currentBlock . NumberU64 ( ) )
return false
}
// Verify the correctness of submitted result.
header := block . Header ( )
header . Nonce = nonce
header . MixDigest = mixDigest
start := time . Now ( )
if ! noverify {
if err := ethash . verifySeal ( nil , header , true ) ; err != nil {
log . Warn ( "Invalid proof-of-work submitted" , "sealhash" , sealhash , "elapsed" , common . PrettyDuration ( time . Since ( start ) ) , "err" , err )
return false
}
}
// Make sure the result channel is assigned.
if results == nil {
log . Warn ( "Ethash result channel is empty, submitted mining result is rejected" )
return false
}
log . Trace ( "Verified correct proof-of-work" , "sealhash" , sealhash , "elapsed" , common . PrettyDuration ( time . Since ( start ) ) )
ethash * Ethash
noverify bool
notifyURLs [ ] string
results chan <- * types . Block
workCh chan * sealTask // Notification channel to push new work and relative result channel to remote sealer
fetchWorkCh chan * sealWork // Channel used for remote sealer to fetch mining work
submitWorkCh chan * mineResult // Channel used for remote sealer to submit their mining result
fetchRateCh chan chan uint64 // Channel used to gather submitted hash rate for local or remote sealer.
submitRateCh chan * hashrate // Channel used for remote sealer to submit their mining hashrate
requestExit chan struct { }
exitCh chan struct { }
}
// Solutions seems to be valid, return to the miner and notify acceptance.
solution := block . WithSeal ( header )
// sealTask wraps a seal block with relative result channel for remote sealer thread.
type sealTask struct {
block * types . Block
results chan <- * types . Block
}
// The submitted solution is within the scope of acceptance.
if solution . NumberU64 ( ) + staleThreshold > currentBlock . NumberU64 ( ) {
select {
case results <- solution :
log . Debug ( "Work submitted is acceptable" , "number" , solution . NumberU64 ( ) , "sealhash" , sealhash , "hash" , solution . Hash ( ) )
return true
default :
log . Warn ( "Sealing result is not read by miner" , "mode" , "remote" , "sealhash" , sealhash )
return false
}
}
// The submitted block is too old to accept, drop it.
log . Warn ( "Work submitted is too old" , "number" , solution . NumberU64 ( ) , "sealhash" , sealhash , "hash" , solution . Hash ( ) )
return false
// mineResult wraps the pow solution parameters for the specified block.
type mineResult struct {
nonce types . BlockNonce
mixDigest common . Hash
hash common . Hash
errc chan error
}
// hashrate wraps the hash rate submitted by the remote sealer.
type hashrate struct {
id common . Hash
ping time . Time
rate uint64
done chan struct { }
}
// sealWork wraps a seal work package for remote sealer.
type sealWork struct {
errc chan error
res chan [ 4 ] string
}
func startRemoteSealer ( ethash * Ethash , urls [ ] string , noverify bool ) * remoteSealer {
ctx , cancel := context . WithCancel ( context . Background ( ) )
s := & remoteSealer {
ethash : ethash ,
noverify : noverify ,
notifyURLs : urls ,
notifyCtx : ctx ,
cancelNotify : cancel ,
works : make ( map [ common . Hash ] * types . Block ) ,
rates : make ( map [ common . Hash ] hashrate ) ,
workCh : make ( chan * sealTask ) ,
fetchWorkCh : make ( chan * sealWork ) ,
submitWorkCh : make ( chan * mineResult ) ,
fetchRateCh : make ( chan chan uint64 ) ,
submitRateCh : make ( chan * hashrate ) ,
requestExit : make ( chan struct { } ) ,
exitCh : make ( chan struct { } ) ,
}
go s . loop ( )
return s
}
func ( s * remoteSealer ) loop ( ) {
defer func ( ) {
s . ethash . config . Log . Trace ( "Ethash remote sealer is exiting" )
s . cancelNotify ( )
s . reqWG . Wait ( )
close ( s . exitCh )
} ( )
ticker := time . NewTicker ( 5 * time . Second )
defer ticker . Stop ( )
for {
select {
case work := <- ethash . workCh :
case work := <- s . workCh :
// Update current work with new received block.
// Note same work can be past twice, happens when changing CPU threads.
results = work . results
s . results = work . results
s . makeWork ( work . block )
s . notifyWork ( )
makeWork ( work . block )
// Notify and requested URLs of the new work availability
notifyWork ( )
case work := <- ethash . fetchWorkCh :
case work := <- s . fetchWorkCh :
// Return current mining work to remote miner.
if currentBlock == nil {
if s . currentBlock == nil {
work . errc <- errNoMiningWork
} else {
work . res <- currentWork
work . res <- s . currentWork
}
case result := <- ethash . submitWorkCh :
case result := <- s . submitWorkCh :
// Verify submitted PoW solution based on maintained mining blocks.
if submitWork ( result . nonce , result . mixDigest , result . hash ) {
if s . submitWork ( result . nonce , result . mixDigest , result . hash ) {
result . errc <- nil
} else {
result . errc <- errInvalidSealResult
}
case result := <- etha sh . submitRateCh :
case result := <- s . submitRateCh :
// Trace remote sealer's hash rate by submitted value.
rates [ result . id ] = hashrate { rate : result . rate , ping : time . Now ( ) }
s . rates [ result . id ] = hashrate { rate : result . rate , ping : time . Now ( ) }
close ( result . done )
case req := <- etha sh . fetchRateCh :
case req := <- s . fetchRateCh :
// Gather all hash rate submitted by remote sealer.
var total uint64
for _ , rate := range rates {
for _ , rate := range s . rates {
// this could overflow
total += rate . rate
}
@ -347,25 +315,126 @@ func (ethash *Ethash) remote(notify []string, noverify bool) {
case <- ticker . C :
// Clear stale submitted hash rate.
for id , rate := range rates {
for id , rate := range s . rates {
if time . Since ( rate . ping ) > 10 * time . Second {
delete ( rates , id )
delete ( s . rates , id )
}
}
// Clear stale pending blocks
if currentBlock != nil {
for hash , block := range works {
if block . NumberU64 ( ) + staleThreshold <= currentBlock . NumberU64 ( ) {
delete ( works , hash )
if s . currentBlock != nil {
for hash , block := range s . works {
if block . NumberU64 ( ) + staleThreshold <= s . currentBlock . NumberU64 ( ) {
delete ( s . works , hash )
}
}
}
case errc := <- ethash . exitCh :
// Exit remote loop if ethash is closed and return relevant error.
errc <- nil
log . Trace ( "Ethash remote sealer is exiting" )
case <- s . requestExit :
return
}
}
}
// makeWork creates a work package for external miner.
//
// The work package consists of 3 strings:
// result[0], 32 bytes hex encoded current block header pow-hash
// result[1], 32 bytes hex encoded seed hash used for DAG
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
// result[3], hex encoded block number
func ( s * remoteSealer ) makeWork ( block * types . Block ) {
hash := s . ethash . SealHash ( block . Header ( ) )
s . currentWork [ 0 ] = hash . Hex ( )
s . currentWork [ 1 ] = common . BytesToHash ( SeedHash ( block . NumberU64 ( ) ) ) . Hex ( )
s . currentWork [ 2 ] = common . BytesToHash ( new ( big . Int ) . Div ( two256 , block . Difficulty ( ) ) . Bytes ( ) ) . Hex ( )
s . currentWork [ 3 ] = hexutil . EncodeBig ( block . Number ( ) )
// Trace the seal work fetched by remote sealer.
s . currentBlock = block
s . works [ hash ] = block
}
// notifyWork notifies all the specified mining endpoints of the availability of
// new work to be processed.
func ( s * remoteSealer ) notifyWork ( ) {
work := s . currentWork
blob , _ := json . Marshal ( work )
s . reqWG . Add ( len ( s . notifyURLs ) )
for _ , url := range s . notifyURLs {
go s . sendNotification ( s . notifyCtx , url , blob , work )
}
}
func ( s * remoteSealer ) sendNotification ( ctx context . Context , url string , json [ ] byte , work [ 4 ] string ) {
defer s . reqWG . Done ( )
req , err := http . NewRequest ( "POST" , url , bytes . NewReader ( json ) )
if err != nil {
s . ethash . config . Log . Warn ( "Can't create remote miner notification" , "err" , err )
return
}
ctx , cancel := context . WithTimeout ( ctx , remoteSealerTimeout )
defer cancel ( )
req = req . WithContext ( ctx )
req . Header . Set ( "Content-Type" , "application/json" )
resp , err := http . DefaultClient . Do ( req )
if err != nil {
s . ethash . config . Log . Warn ( "Failed to notify remote miner" , "err" , err )
} else {
s . ethash . config . Log . Trace ( "Notified remote miner" , "miner" , url , "hash" , work [ 0 ] , "target" , work [ 2 ] )
resp . Body . Close ( )
}
}
// submitWork verifies the submitted pow solution, returning
// whether the solution was accepted or not (not can be both a bad pow as well as
// any other error, like no pending work or stale mining result).
func ( s * remoteSealer ) submitWork ( nonce types . BlockNonce , mixDigest common . Hash , sealhash common . Hash ) bool {
if s . currentBlock == nil {
s . ethash . config . Log . Error ( "Pending work without block" , "sealhash" , sealhash )
return false
}
// Make sure the work submitted is present
block := s . works [ sealhash ]
if block == nil {
s . ethash . config . Log . Warn ( "Work submitted but none pending" , "sealhash" , sealhash , "curnumber" , s . currentBlock . NumberU64 ( ) )
return false
}
// Verify the correctness of submitted result.
header := block . Header ( )
header . Nonce = nonce
header . MixDigest = mixDigest
start := time . Now ( )
if ! s . noverify {
if err := s . ethash . verifySeal ( nil , header , true ) ; err != nil {
s . ethash . config . Log . Warn ( "Invalid proof-of-work submitted" , "sealhash" , sealhash , "elapsed" , common . PrettyDuration ( time . Since ( start ) ) , "err" , err )
return false
}
}
// Make sure the result channel is assigned.
if s . results == nil {
s . ethash . config . Log . Warn ( "Ethash result channel is empty, submitted mining result is rejected" )
return false
}
s . ethash . config . Log . Trace ( "Verified correct proof-of-work" , "sealhash" , sealhash , "elapsed" , common . PrettyDuration ( time . Since ( start ) ) )
// Solutions seems to be valid, return to the miner and notify acceptance.
solution := block . WithSeal ( header )
// The submitted solution is within the scope of acceptance.
if solution . NumberU64 ( ) + staleThreshold > s . currentBlock . NumberU64 ( ) {
select {
case s . results <- solution :
s . ethash . config . Log . Debug ( "Work submitted is acceptable" , "number" , solution . NumberU64 ( ) , "sealhash" , sealhash , "hash" , solution . Hash ( ) )
return true
default :
s . ethash . config . Log . Warn ( "Sealing result is not read by miner" , "mode" , "remote" , "sealhash" , sealhash )
return false
}
}
// The submitted block is too old to accept, drop it.
s . ethash . config . Log . Warn ( "Work submitted is too old" , "number" , solution . NumberU64 ( ) , "sealhash" , sealhash , "hash" , solution . Hash ( ) )
return false
}