miner, ethash: push empty block to sealer without waiting execution

This commit is contained in:
rjl493456442 2018-01-31 14:31:36 +08:00
parent d2d3b84fa7
commit dd83e3d40a
12 changed files with 338 additions and 202 deletions

View file

@ -31,7 +31,7 @@ import (
) )
const ( const (
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0" ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
) )

View file

@ -672,6 +672,11 @@ func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
return new(big.Int).Set(diffNoTurn) return new(big.Int).Set(diffNoTurn)
} }
// Close implements consensus.Engine, returning internal error and close the clique.
func (c *Clique) Close() error {
return nil
}
// APIs implements consensus.Engine, returning the user facing RPC API to allow // APIs implements consensus.Engine, returning the user facing RPC API to allow
// controlling the signer voting. // controlling the signer voting.
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API { func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {

View file

@ -96,6 +96,9 @@ type Engine interface {
// APIs returns the RPC APIs this consensus engine provides. // APIs returns the RPC APIs this consensus engine provides.
APIs(chain ChainReader) []rpc.API APIs(chain ChainReader) []rpc.API
// Close closes the consensus engine.
Close() error
} }
// PoW is a consensus engine based on proof-of-work. // PoW is a consensus engine based on proof-of-work.

View file

@ -730,6 +730,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) {
go func(idx int) { go func(idx int) {
defer pend.Done() defer pend.Done()
ethash := New(Config{cachedir, 0, 1, "", 0, 0, ModeNormal}) ethash := New(Config{cachedir, 0, 1, "", 0, 0, ModeNormal})
defer ethash.Close()
if err := ethash.VerifySeal(nil, block.Header()); err != nil { if err := ethash.VerifySeal(nil, block.Header()); err != nil {
t.Errorf("proc %d: block verification failed: %v", idx, err) t.Errorf("proc %d: block verification failed: %v", idx, err)
} }

View file

@ -13,78 +13,87 @@
// //
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package ethash package ethash
import ( import (
"time" "errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
// API exposes ethash related methods for the RPC interface var errEthashStopped = errors.New("ethash stopped")
// API exposes ethash related methods for the RPC interface.
type API struct { type API struct {
ethash *Ethash ethash *Ethash
} }
// GetWork returns a work package for external miner. The work package consists of 3 strings // GetWork returns a work package for external miner.
// result[0], 32 bytes hex encoded current block header pow-hash //
// result[1], 32 bytes hex encoded seed hash used for DAG // The work package consists of 3 strings:
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty // result[0] - 32 bytes hex encoded current block header pow-hash
func (s *API) GetWork() ([3]string, error) { // result[1] - 32 bytes hex encoded seed hash used for DAG
// result[2] - 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
func (api *API) GetWork() ([3]string, error) {
var ( var (
workCh = make(chan [3]string) workCh = make(chan [3]string, 1)
errCh = make(chan error) errCh = make(chan error, 1)
err error
) )
op := &remoteOp{
typ: opGetWork, select {
workCh: workCh, case api.ethash.fetchWorkCh <- &sealWork{errCh: errCh, resCh: workCh}:
errCh: errCh, case <-api.ethash.exitCh:
return [3]string{}, errEthashStopped
} }
s.ethash.remoteOp <- op
if err := <-errCh; err == nil { if err = <-errCh; err == nil {
return <-workCh, nil return <-workCh, nil
} else { }
return [3]string{}, err return [3]string{}, err
} }
}
// SubmitWork can be used by external miner to submit their POW solution. It returns an indication if the work was // SubmitWork can be used by external miner to submit their POW solution.
// accepted. Note, this is not an indication if the provided work was valid! // It returns an indication if the work was accepted.
func (s *API) SubmitWork(nonce types.BlockNonce, solution, digest common.Hash) bool { // Note either an invalid solution, a stale work a non-existent work will return false.
var errCh = make(chan error) func (api *API) SubmitWork(nonce types.BlockNonce, hash, digest common.Hash) bool {
op := &remoteOp{ var errCh = make(chan error, 1)
typ: opSubmitWork,
result: mineResult{ select {
case api.ethash.submitWorkCh <- &mineResult{
nonce: nonce, nonce: nonce,
mixDigest: digest, mixDigest: digest,
hash: solution, hash: hash,
},
errCh: errCh, errCh: errCh,
} }:
s.ethash.remoteOp <- op case <-api.ethash.exitCh:
if err := <-errCh; err == nil {
return true
} else {
return false return false
} }
err := <-errCh
return err == nil
} }
// SubmitHashrate can be used for remote miners to submit their hash rate. This enables the node to report the combined // SubmitHashrate can be used for remote miners to submit their hash rate.
// hash rate of all miners which submit work through this node. It accepts the miner hash rate and an identifier which // This enables the node to report the combined hash rate of all miners
// must be unique between nodes. // which submit work through this node.
func (s *API) SubmitHashRate(r hexutil.Uint64, id common.Hash) bool { //
submit := func(rate map[common.Hash]hashrate) { // It accepts the miner hash rate and an identifier which must be unique
rate[id] = hashrate{rate: uint64(r), ping: time.Now()} // between nodes.
func (api *API) SubmitHashRate(rate hexutil.Uint64, id common.Hash) bool {
var doneCh = make(chan struct{}, 1)
select {
case api.ethash.submitRateCh <- &hashrate{done: doneCh, rate: uint64(rate), id: id}:
case <-api.ethash.exitCh:
return false
} }
errCh := make(chan error)
op := &remoteOp{ // Block until hash rate submitted successfully.
typ: opHashrate, <-doneCh
rateOp: submit,
errCh: errCh,
}
s.ethash.remoteOp <- op
<-errCh
return true return true
} }

View file

@ -391,38 +391,28 @@ type Config struct {
PowMode Mode PowMode Mode
} }
// remoteOpType defines all the types of operations related to remote sealer.
type remoteOpType uint
const (
opGetWork remoteOpType = iota
opSubmitWork
opHashrate
)
// mineResult wraps the pow solution parameters for the specified block. // mineResult wraps the pow solution parameters for the specified block.
type mineResult struct { type mineResult struct {
nonce types.BlockNonce nonce types.BlockNonce
mixDigest common.Hash mixDigest common.Hash
hash common.Hash hash common.Hash
errCh chan error
} }
// hashrate wraps the hash rate submitted by the remote sealer.
type hashrate struct { type hashrate struct {
id common.Hash id common.Hash
ping time.Time ping time.Time
rate uint64 rate uint64
done chan struct{}
} }
type hashrateOp func(map[common.Hash]hashrate) // sealWork wraps a seal work package for remote sealer.
type sealWork struct {
// remoteOp wraps a mining operation sent by remote miner.
type remoteOp struct {
typ remoteOpType
result mineResult
errCh chan error errCh chan error
workCh chan [3]string resCh chan [3]string
rateOp hashrateOp
} }
// Ethash is a consensus engine based on proof-of-work implementing the ethash // Ethash is a consensus engine based on proof-of-work implementing the ethash
@ -433,8 +423,6 @@ type Ethash struct {
caches *lru // In memory caches to avoid regenerating too often caches *lru // In memory caches to avoid regenerating too often
datasets *lru // In memory datasets to avoid regenerating too often datasets *lru // In memory datasets to avoid regenerating too often
exitCh chan struct{} // Notification channel to exiting backend threads
// Mining related fields // Mining related fields
rand *rand.Rand // Properly seeded random source for nonces rand *rand.Rand // Properly seeded random source for nonces
threads int // Number of threads to mine on if mining threads int // Number of threads to mine on if mining
@ -444,13 +432,19 @@ type Ethash struct {
// Remote sealer related fields // Remote sealer related fields
workCh chan *types.Block // Notification channel to push new work to remote sealer workCh chan *types.Block // Notification channel to push new work to remote sealer
resultCh chan *types.Block // Channel used by mining threads to return result resultCh chan *types.Block // Channel used by mining threads to return result
remoteOp chan *remoteOp // Channel used to receive all mining operations from api 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
// The fields below are hooks for testing // The fields below are hooks for testing
shared *Ethash // Shared PoW verifier to avoid cache regeneration shared *Ethash // Shared PoW verifier to avoid cache regeneration
fakeFail uint64 // Block number which fails PoW check even in fake mode fakeFail uint64 // Block number which fails PoW check even in fake mode
fakeDelay time.Duration // Time delay to sleep for before returning from verify fakeDelay time.Duration // Time delay to sleep for before returning from verify
lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields
closeOnce sync.Once // Ensures exit channel will not be closed twice.
exitCh chan chan error // Notification channel to exiting backend threads
} }
// New creates a full sized ethash PoW scheme. // New creates a full sized ethash PoW scheme.
@ -471,13 +465,15 @@ func New(config Config) *Ethash {
datasets: newlru("dataset", config.DatasetsInMem, newDataset), datasets: newlru("dataset", config.DatasetsInMem, newDataset),
update: make(chan struct{}), update: make(chan struct{}),
hashrate: metrics.NewMeter(), hashrate: metrics.NewMeter(),
exitCh: make(chan struct{}),
workCh: make(chan *types.Block), workCh: make(chan *types.Block),
resultCh: make(chan *types.Block), resultCh: make(chan *types.Block),
remoteOp: make(chan *remoteOp), fetchWorkCh: make(chan *sealWork),
submitWorkCh: make(chan *mineResult),
fetchRateCh: make(chan chan uint64),
submitRateCh: make(chan *hashrate),
exitCh: make(chan chan error),
} }
go ethash.remote(ethash.resultCh) go ethash.remote()
runtime.SetFinalizer(ethash, (*Ethash).close)
return ethash return ethash
} }
@ -490,13 +486,15 @@ func NewTester() *Ethash {
datasets: newlru("dataset", 1, newDataset), datasets: newlru("dataset", 1, newDataset),
update: make(chan struct{}), update: make(chan struct{}),
hashrate: metrics.NewMeter(), hashrate: metrics.NewMeter(),
exitCh: make(chan struct{}),
workCh: make(chan *types.Block), workCh: make(chan *types.Block),
resultCh: make(chan *types.Block), resultCh: make(chan *types.Block),
remoteOp: make(chan *remoteOp), fetchWorkCh: make(chan *sealWork),
submitWorkCh: make(chan *mineResult),
fetchRateCh: make(chan chan uint64),
submitRateCh: make(chan *hashrate),
exitCh: make(chan chan error),
} }
go ethash.remote(ethash.resultCh) go ethash.remote()
runtime.SetFinalizer(ethash, (*Ethash).close)
return ethash return ethash
} }
@ -508,6 +506,7 @@ func NewFaker() *Ethash {
config: Config{ config: Config{
PowMode: ModeFake, PowMode: ModeFake,
}, },
exitCh: make(chan chan error),
} }
} }
@ -520,6 +519,7 @@ func NewFakeFailer(fail uint64) *Ethash {
PowMode: ModeFake, PowMode: ModeFake,
}, },
fakeFail: fail, fakeFail: fail,
exitCh: make(chan chan error),
} }
} }
@ -532,6 +532,7 @@ func NewFakeDelayer(delay time.Duration) *Ethash {
PowMode: ModeFake, PowMode: ModeFake,
}, },
fakeDelay: delay, fakeDelay: delay,
exitCh: make(chan chan error),
} }
} }
@ -542,19 +543,32 @@ func NewFullFaker() *Ethash {
config: Config{ config: Config{
PowMode: ModeFullFake, PowMode: ModeFullFake,
}, },
exitCh: make(chan chan error),
} }
} }
// NewShared creates a full sized ethash PoW shared between all requesters running // NewShared creates a full sized ethash PoW shared between all requesters running
// in the same process. // in the same process.
func NewShared() *Ethash { func NewShared() *Ethash {
return &Ethash{shared: sharedEthash} return &Ethash{
shared: sharedEthash,
exitCh: make(chan chan error),
}
} }
// close closes the exit channel to notify all backend threads exiting. // Close closes the exit channel to notify all backend threads exiting.
func (ethash *Ethash) close() { func (ethash *Ethash) Close() error {
runtime.SetFinalizer(ethash, nil) var err error
ethash.closeOnce.Do(func() {
var errCh = make(chan error)
select {
case ethash.exitCh <- errCh:
err = <-errCh
close(ethash.exitCh) close(ethash.exitCh)
default:
}
})
return err
} }
// cache tries to retrieve a verification cache for the specified block number // cache tries to retrieve a verification cache for the specified block number
@ -632,37 +646,38 @@ func (ethash *Ethash) SetThreads(threads int) {
// Note the returned hashrate includes local hashrate, but also includes the total // Note the returned hashrate includes local hashrate, but also includes the total
// hashrate of all remote miner. // hashrate of all remote miner.
func (ethash *Ethash) Hashrate() float64 { func (ethash *Ethash) Hashrate() float64 {
var ( var resCh = make(chan uint64, 1)
total uint64
errCh = make(chan error)
)
// getHashrate gathers all remote miner's hashrate.
getHashrate := func(hashrate map[common.Hash]hashrate) {
for _, rate := range hashrate {
// this could overflow
total += rate.rate
}
return
}
op := &remoteOp{
typ: opHashrate,
rateOp: getHashrate,
errCh: errCh,
}
ethash.remoteOp <- op
<-errCh
select {
case ethash.fetchRateCh <- resCh:
case <-ethash.exitCh:
// Return local hashrate only if ethash is stopped.
return ethash.hashrate.Rate1()
}
// Gather total submitted hash rate of remote sealers.
total := <-resCh
return ethash.hashrate.Rate1() + float64(total) return ethash.hashrate.Rate1() + float64(total)
} }
// APIs implements consensus.Engine, returning the user facing RPC APIs. // APIs implements consensus.Engine, returning the user facing RPC APIs.
func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API { func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API {
return []rpc.API{{ // In order to ensure backward compatibility, we exposes ethash RPC APIs
// to both eth and ethash namespaces.
return []rpc.API{
{
Namespace: "eth", Namespace: "eth",
Version: "1.0", Version: "1.0",
Service: &API{ethash}, Service: &API{ethash},
Public: true, Public: true,
}} },
{
Namespace: "ethash",
Version: "1.0",
Service: &API{ethash},
Public: true,
},
}
} }
// SeedHash is the seed to use for generating a verification cache and the mining // SeedHash is the seed to use for generating a verification cache and the mining

View file

@ -23,6 +23,7 @@ import (
"os" "os"
"sync" "sync"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@ -34,6 +35,7 @@ func TestTestMode(t *testing.T) {
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)} head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
ethash := NewTester() ethash := NewTester()
defer ethash.Close()
block, err := ethash.Seal(nil, types.NewBlockWithHeader(head), nil) block, err := ethash.Seal(nil, types.NewBlockWithHeader(head), nil)
if err != nil { if err != nil {
t.Fatalf("failed to seal block: %v", err) t.Fatalf("failed to seal block: %v", err)
@ -54,6 +56,7 @@ func TestCacheFileEvict(t *testing.T) {
} }
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
e := New(Config{CachesInMem: 3, CachesOnDisk: 10, CacheDir: tmpdir, PowMode: ModeTest}) e := New(Config{CachesInMem: 3, CachesOnDisk: 10, CacheDir: tmpdir, PowMode: ModeTest})
defer e.Close()
workers := 8 workers := 8
epochs := 100 epochs := 100
@ -82,14 +85,16 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) {
func TestRemoteSealer(t *testing.T) { func TestRemoteSealer(t *testing.T) {
ethash := NewTester() ethash := NewTester()
defer ethash.Close()
api := &API{ethash} api := &API{ethash}
if _, err := api.GetWork(); err != errNoMineWork { if _, err := api.GetWork(); err != errNoMiningWork {
t.Error("expected to return an error indicate there is no mining work") t.Error("expect to return an error indicate there is no mining work")
} }
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)} head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(head) block := types.NewBlockWithHeader(head)
// Push new work
// Push new work.
ethash.Seal(nil, block, nil) ethash.Seal(nil, block, nil)
var ( var (
@ -97,11 +102,29 @@ func TestRemoteSealer(t *testing.T) {
err error err error
) )
if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() { if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() {
t.Error("expected to return a mining work has same hash") t.Error("expect to return a mining work has same hash")
} }
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res { if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
t.Error("expected to return false when submit a fake solution") t.Error("expect to return false when submit a fake solution")
}
// Push new block with same block number to replace the original one.
head = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
block = types.NewBlockWithHeader(head)
ethash.Seal(nil, block, nil)
if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() {
t.Error("expect to return the latest pushed work")
}
// Push block with higher block number.
newHead := &types.Header{Number: big.NewInt(2), Difficulty: big.NewInt(100)}
newBlock := types.NewBlockWithHeader(newHead)
ethash.Seal(nil, newBlock, nil)
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
t.Error("expect to return false when submit a stale solution")
} }
} }
@ -113,6 +136,13 @@ func TestHashRate(t *testing.T) {
expect uint64 expect uint64
ids = []common.Hash{common.HexToHash("a"), common.HexToHash("b"), common.HexToHash("c")} ids = []common.Hash{common.HexToHash("a"), common.HexToHash("b"), common.HexToHash("c")}
) )
defer ethash.Close()
if tot := ethash.Hashrate(); tot != 0 {
t.Error("expect the result should be zero")
}
for i := 0; i < len(hashrate); i += 1 { for i := 0; i < len(hashrate); i += 1 {
if res := api.SubmitHashRate(hashrate[i], ids[i]); !res { if res := api.SubmitHashRate(hashrate[i], ids[i]); !res {
t.Error("remote miner submit hashrate failed") t.Error("remote miner submit hashrate failed")
@ -123,3 +153,19 @@ func TestHashRate(t *testing.T) {
t.Error("expect total hashrate should be same") t.Error("expect total hashrate should be same")
} }
} }
func TestClosedRemoteSealer(t *testing.T) {
ethash := NewTester()
// Make sure exit channel has been listened
time.Sleep(1 * time.Second)
ethash.Close()
api := &API{ethash}
if _, err := api.GetWork(); err != errEthashStopped {
t.Error("expect to return an error to indicate ethash is stopped")
}
if res := api.SubmitHashRate(hexutil.Uint64(100), common.HexToHash("a")); res {
t.Error("expect to return false when submit hashrate to a stopped ethash")
}
}

View file

@ -33,9 +33,8 @@ import (
) )
var ( var (
errNoMineWork = errors.New("No mining work available yet, don't panic.") errNoMiningWork = errors.New("no mining work available yet, don't panic")
errInvalidSealResult = errors.New("Invalid or stale proof-of-work solution.") errInvalidSealResult = errors.New("invalid or stale proof-of-work solution")
errUndefinedRemoteOp = errors.New("Undefined remote mining operation.")
) )
// Seal implements consensus.Engine, attempting to find a nonce that satisfies // Seal implements consensus.Engine, attempting to find a nonce that satisfies
@ -51,7 +50,6 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
if ethash.shared != nil { if ethash.shared != nil {
return ethash.shared.Seal(chain, block, stop) return ethash.shared.Seal(chain, block, stop)
} }
// Create a runner and the multiple search threads it directs // Create a runner and the multiple search threads it directs
abort := make(chan struct{}) abort := make(chan struct{})
@ -72,10 +70,10 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
if threads < 0 { if threads < 0 {
threads = 0 // Allows disabling local mining without extra logic around local/remote threads = 0 // Allows disabling local mining without extra logic around local/remote
} }
// Push new work to remote sealer // Push new work to remote sealer
if ethash.workCh != nil {
ethash.workCh <- block ethash.workCh <- block
}
var pend sync.WaitGroup var pend sync.WaitGroup
for i := 0; i < threads; i++ { for i := 0; i < threads; i++ {
pend.Add(1) pend.Add(1)
@ -164,61 +162,72 @@ search:
} }
// remote starts a standalone goroutine to handle remote mining related stuff. // remote starts a standalone goroutine to handle remote mining related stuff.
func (ethash *Ethash) remote(resultCh chan *types.Block) { func (ethash *Ethash) remote() {
var ( var (
works = make(map[common.Hash]*types.Block) works = make(map[common.Hash]*types.Block)
hashrate = make(map[common.Hash]hashrate) rates = make(map[common.Hash]hashrate)
currentWork *types.Block currentWork *types.Block
) )
// getWork returns a work package for external miner. The work package consists of 3 strings // getWork returns 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[0], 32 bytes hex encoded current block header pow-hash
// result[1], 32 bytes hex encoded seed hash used for DAG // result[1], 32 bytes hex encoded seed hash used for DAG
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty // result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
getWork := func() ([3]string, error) { getWork := func() ([3]string, error) {
var res [3]string var res [3]string
if currentWork == nil { if currentWork == nil {
return res, errNoMineWork return res, errNoMiningWork
} }
res[0] = currentWork.HashNoNonce().Hex() res[0] = currentWork.HashNoNonce().Hex()
res[1] = common.BytesToHash(SeedHash(currentWork.NumberU64())).Hex() res[1] = common.BytesToHash(SeedHash(currentWork.NumberU64())).Hex()
// Calculate the "target" to be returned to the external sealer
// Calculate the "target" to be returned to the external sealer.
n := big.NewInt(1) n := big.NewInt(1)
n.Lsh(n, 255) n.Lsh(n, 255)
n.Div(n, currentWork.Difficulty()) n.Div(n, currentWork.Difficulty())
n.Lsh(n, 1) n.Lsh(n, 1)
res[2] = common.BytesToHash(n.Bytes()).Hex() res[2] = common.BytesToHash(n.Bytes()).Hex()
// Trace the seal work fetched by remote sealer.
works[currentWork.HashNoNonce()] = currentWork works[currentWork.HashNoNonce()] = currentWork
return res, nil return res, nil
} }
// submitWork verifies the submitted pow solution, returning // submitWork verifies the submitted pow solution, returning
// whether the solution was accepted or not (not can be both a bad pow as well as // whether the solution was accepted or not (not can be both a bad pow as well as
// any other error, like no work pending). // any other error, like no pending work or stale mining result).
submitWork := func(result mineResult) bool { submitWork := func(nonce types.BlockNonce, mixDigest common.Hash, hash common.Hash) bool {
// Make sure the work submitted is present // Make sure the work submitted is present
block := works[result.hash] block := works[hash]
if block == nil { if block == nil {
log.Info("Work submitted but none pending", "hash", result.hash) log.Info("Work submitted but none pending", "hash", hash)
return false return false
} }
// Make sure the Engine solutions is indeed valid
// Verify the correctness of submitted result.
header := block.Header() header := block.Header()
header.Nonce = result.nonce header.Nonce = nonce
header.MixDigest = result.mixDigest header.MixDigest = mixDigest
if err := ethash.VerifySeal(nil, header); err != nil { if err := ethash.VerifySeal(nil, header); err != nil {
log.Warn("Invalid proof-of-work submitted", "hash", result.hash, "err", err) log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
return false return false
} }
// Solutions seems to be valid, return to the miner and notify acceptance // Make sure the result channel is created.
if ethash.resultCh == nil {
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
return false
}
// Solutions seems to be valid, return to the miner and notify acceptance.
select { select {
case resultCh <- block.WithSeal(header): case ethash.resultCh <- block.WithSeal(header):
delete(works, result.hash) delete(works, hash)
return true return true
default: default:
log.Info("Work submitted is stale", "hash", result.hash) log.Info("Work submitted is stale", "hash", hash)
return false return false
} }
} }
@ -234,48 +243,53 @@ running:
// Start new round mining, throw out all previous work. // Start new round mining, throw out all previous work.
works = make(map[common.Hash]*types.Block) works = make(map[common.Hash]*types.Block)
} }
// Update current work with new received block // Update current work with new received block.
// Note same work can be past twice, happens when changing CPU threads. // Note same work can be past twice, happens when changing CPU threads.
currentWork = block currentWork = block
case op := <-ethash.remoteOp: case work := <-ethash.fetchWorkCh:
switch op.typ { // Return current mining work to remote miner.
case opGetWork: miningWork, err := getWork()
// Return current mining work to remote miner
work, err := getWork()
if err != nil { if err != nil {
op.errCh <- err work.errCh <- err
} else { } else {
close(op.errCh) close(work.errCh)
op.workCh <- work work.resCh <- miningWork
} }
case opSubmitWork:
// Verify submitted PoW solution based on maintained mining blocks case result := <-ethash.submitWorkCh:
if submitWork(op.result) { // Verify submitted PoW solution based on maintained mining blocks.
close(op.errCh) if submitWork(result.nonce, result.mixDigest, result.hash) {
close(result.errCh)
} else { } else {
op.errCh <- errInvalidSealResult result.errCh <- errInvalidSealResult
} }
case opHashrate:
// This case is used by remote miner hashrate operation case result := <-ethash.submitRateCh:
if op.rateOp != nil { // Trace remote sealer's hash rate by submitted value.
op.rateOp(hashrate) rates[result.id] = hashrate{rate: result.rate, ping: time.Now()}
close(op.errCh) close(result.done)
}
default: case req := <-ethash.fetchRateCh:
op.errCh <- errUndefinedRemoteOp // Gather all hash rate submitted by remote sealer.
var total uint64
for _, rate := range rates {
// this could overflow
total += rate.rate
} }
req <- total
case <-ticker.C: case <-ticker.C:
// Clear stale submited hashrate // Clear stale submitted hash rate.
for id, rate := range hashrate { for id, rate := range rates {
if time.Since(rate.ping) > 10*time.Second { if time.Since(rate.ping) > 10*time.Second {
delete(hashrate, id) delete(rates, id)
} }
} }
case <-ethash.exitCh: case errCh := <-ethash.exitCh:
// Exit remote loop if ethash object is cleared by GC // Exit remote loop if ethash is closed and return relevant error.
errCh <- nil
break running break running
} }
} }

View file

@ -411,6 +411,7 @@ func (s *Ethereum) Start(srvr *p2p.Server) error {
func (s *Ethereum) Stop() error { func (s *Ethereum) Stop() error {
s.bloomIndexer.Close() s.bloomIndexer.Close()
s.blockchain.Stop() s.blockchain.Stop()
s.engine.Close()
s.protocolManager.Stop() s.protocolManager.Stop()
if s.lesServer != nil { if s.lesServer != nil {
s.lesServer.Stop() s.lesServer.Stop()
@ -421,6 +422,5 @@ func (s *Ethereum) Stop() error {
s.chainDb.Close() s.chainDb.Close()
close(s.shutdownChan) close(s.shutdownChan)
return nil return nil
} }

View file

@ -21,6 +21,7 @@ var Modules = map[string]string{
"admin": Admin_JS, "admin": Admin_JS,
"chequebook": Chequebook_JS, "chequebook": Chequebook_JS,
"clique": Clique_JS, "clique": Clique_JS,
"ethash": Ethash_JS,
"debug": Debug_JS, "debug": Debug_JS,
"eth": Eth_JS, "eth": Eth_JS,
"miner": Miner_JS, "miner": Miner_JS,
@ -109,6 +110,29 @@ web3._extend({
}); });
` `
const Ethash_JS = `
web3._extend({
property: 'ethash',
methods: [
new web3._extend.Method({
name: 'getWork',
call: 'ethash_getWork',
params: 0
}),
new web3._extend.Method({
name: 'submitWork',
call: 'ethash_submitWork',
params: 3,
}),
new web3._extend.Method({
name: 'submitHashRate',
call: 'ethash_submitHashRate',
params: 2,
}),
]
});
`
const Admin_JS = ` const Admin_JS = `
web3._extend({ web3._extend({
property: 'admin', property: 'admin',

View file

@ -248,6 +248,7 @@ func (s *LightEthereum) Stop() error {
s.blockchain.Stop() s.blockchain.Stop()
s.protocolManager.Stop() s.protocolManager.Stop()
s.txPool.Stop() s.txPool.Stop()
s.engine.Close()
s.eventMux.Stop() s.eventMux.Stop()

View file

@ -447,13 +447,6 @@ func (self *worker) commitNewWork() {
if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 { if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 {
misc.ApplyDAOHardFork(work.state) misc.ApplyDAOHardFork(work.state)
} }
pending, err := self.eth.TxPool().Pending()
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
return
}
txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending)
work.commitTransactions(self.mux, txs, self.chain, self.coinbase)
// compute uncles for the new block. // compute uncles for the new block.
var ( var (
@ -477,16 +470,41 @@ func (self *worker) commitNewWork() {
for _, hash := range badUncles { for _, hash := range badUncles {
delete(self.possibleUncles, hash) delete(self.possibleUncles, hash)
} }
// Create the new block to seal with the consensus engine
// Create an empty block based on temporary copied state for sealing in advance without waiting block
// execution finished.
if work.Block, err = self.engine.Finalize(self.chain, header, work.state.Copy(), nil, uncles, nil); err != nil {
log.Error("Failed to finalize block for temporary sealing", "err", err)
} else {
// Push empty work in advance without applying pending transaction.
// The reason is transactions execution can cost a lot and sealer need to
// take advantage of this part time.
if atomic.LoadInt32(&self.mining) == 1 {
log.Info("Commit new empty mining work", "number", work.Block.Number(), "uncles", len(uncles))
}
self.push(work)
}
// Fill the block with all available pending transactions.
pending, err := self.eth.TxPool().Pending()
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
return
}
txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending)
work.commitTransactions(self.mux, txs, self.chain, self.coinbase)
// Create the full block to seal with the consensus engine
if work.Block, err = self.engine.Finalize(self.chain, header, work.state, work.txs, uncles, work.receipts); err != nil { if work.Block, err = self.engine.Finalize(self.chain, header, work.state, work.txs, uncles, work.receipts); err != nil {
log.Error("Failed to finalize block for sealing", "err", err) log.Error("Failed to finalize block for sealing", "err", err)
return return
} }
// We only care about logging if we're actually mining. // We only care about logging if we're actually mining.
if atomic.LoadInt32(&self.mining) == 1 { if atomic.LoadInt32(&self.mining) == 1 {
log.Info("Commit new mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart))) log.Info("Commit new full mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart)))
self.unconfirmed.Shift(work.Block.NumberU64() - 1) self.unconfirmed.Shift(work.Block.NumberU64() - 1)
} }
// Push full work to sealer, which will replace the empty work sent before automatically.
self.push(work) self.push(work)
self.updateSnapshot() self.updateSnapshot()
} }