mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
consensus/ethash: expose submitHashrate api
This commit is contained in:
parent
9833ccd3f6
commit
d2d3b84fa7
9 changed files with 179 additions and 308 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2018 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -16,7 +16,10 @@
|
||||||
package ethash
|
package ethash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"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/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -35,13 +38,12 @@ func (s *API) GetWork() ([3]string, error) {
|
||||||
errCh = make(chan error)
|
errCh = make(chan error)
|
||||||
)
|
)
|
||||||
op := &remoteOp{
|
op := &remoteOp{
|
||||||
typ: getWork,
|
typ: opGetWork,
|
||||||
workCh: workCh,
|
workCh: workCh,
|
||||||
errCh: errCh,
|
errCh: errCh,
|
||||||
}
|
}
|
||||||
s.ethash.remoteOp <- op
|
s.ethash.remoteOp <- op
|
||||||
err := <-errCh
|
if err := <-errCh; err == nil {
|
||||||
if err == nil {
|
|
||||||
return <-workCh, nil
|
return <-workCh, nil
|
||||||
} else {
|
} else {
|
||||||
return [3]string{}, err
|
return [3]string{}, err
|
||||||
|
|
@ -52,14 +54,13 @@ func (s *API) GetWork() ([3]string, error) {
|
||||||
// accepted. Note, this is not an indication if the provided work was valid!
|
// accepted. Note, this is not an indication if the provided work was valid!
|
||||||
func (s *API) SubmitWork(nonce types.BlockNonce, solution, digest common.Hash) bool {
|
func (s *API) SubmitWork(nonce types.BlockNonce, solution, digest common.Hash) bool {
|
||||||
var errCh = make(chan error)
|
var errCh = make(chan error)
|
||||||
result := sealResult{
|
op := &remoteOp{
|
||||||
|
typ: opSubmitWork,
|
||||||
|
result: mineResult{
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
mixDigest: digest,
|
mixDigest: digest,
|
||||||
hash: solution,
|
hash: solution,
|
||||||
}
|
},
|
||||||
op := &remoteOp{
|
|
||||||
typ: submitWork,
|
|
||||||
result: result,
|
|
||||||
errCh: errCh,
|
errCh: errCh,
|
||||||
}
|
}
|
||||||
s.ethash.remoteOp <- op
|
s.ethash.remoteOp <- op
|
||||||
|
|
@ -69,3 +70,21 @@ func (s *API) SubmitWork(nonce types.BlockNonce, solution, digest common.Hash) b
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubmitHashrate can be used for remote miners to submit their hash rate. This enables the node to report the combined
|
||||||
|
// hash rate of all miners which submit work through this node. It accepts the miner hash rate and an identifier which
|
||||||
|
// must be unique between nodes.
|
||||||
|
func (s *API) SubmitHashRate(r hexutil.Uint64, id common.Hash) bool {
|
||||||
|
submit := func(rate map[common.Hash]hashrate) {
|
||||||
|
rate[id] = hashrate{rate: uint64(r), ping: time.Now()}
|
||||||
|
}
|
||||||
|
errCh := make(chan error)
|
||||||
|
op := &remoteOp{
|
||||||
|
typ: opHashrate,
|
||||||
|
rateOp: submit,
|
||||||
|
errCh: errCh,
|
||||||
|
}
|
||||||
|
s.ethash.remoteOp <- op
|
||||||
|
<-errCh
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -395,23 +395,34 @@ type Config struct {
|
||||||
type remoteOpType uint
|
type remoteOpType uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
getWork remoteOpType = iota
|
opGetWork remoteOpType = iota
|
||||||
submitWork
|
opSubmitWork
|
||||||
|
opHashrate
|
||||||
)
|
)
|
||||||
|
|
||||||
// sealResult wraps the pow solution parameters for the specified block.
|
// mineResult wraps the pow solution parameters for the specified block.
|
||||||
type sealResult struct {
|
type mineResult struct {
|
||||||
nonce types.BlockNonce
|
nonce types.BlockNonce
|
||||||
mixDigest common.Hash
|
mixDigest common.Hash
|
||||||
hash common.Hash
|
hash common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type hashrate struct {
|
||||||
|
id common.Hash
|
||||||
|
ping time.Time
|
||||||
|
rate uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type hashrateOp func(map[common.Hash]hashrate)
|
||||||
|
|
||||||
// remoteOp wraps a mining operation sent by remote miner.
|
// remoteOp wraps a mining operation sent by remote miner.
|
||||||
type remoteOp struct {
|
type remoteOp struct {
|
||||||
typ remoteOpType
|
typ remoteOpType
|
||||||
result sealResult
|
result mineResult
|
||||||
errCh chan error
|
errCh chan error
|
||||||
workCh chan [3]string
|
workCh 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
|
||||||
|
|
@ -434,7 +445,6 @@ type Ethash struct {
|
||||||
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
|
remoteOp chan *remoteOp // Channel used to receive all mining operations from api
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -474,7 +484,20 @@ func New(config Config) *Ethash {
|
||||||
// NewTester creates a small sized ethash PoW scheme useful only for testing
|
// NewTester creates a small sized ethash PoW scheme useful only for testing
|
||||||
// purposes.
|
// purposes.
|
||||||
func NewTester() *Ethash {
|
func NewTester() *Ethash {
|
||||||
return New(Config{CachesInMem: 1, PowMode: ModeTest})
|
ethash := &Ethash{
|
||||||
|
config: Config{PowMode: ModeTest},
|
||||||
|
caches: newlru("cache", 1, newCache),
|
||||||
|
datasets: newlru("dataset", 1, newDataset),
|
||||||
|
update: make(chan struct{}),
|
||||||
|
hashrate: metrics.NewMeter(),
|
||||||
|
exitCh: make(chan struct{}),
|
||||||
|
workCh: make(chan *types.Block),
|
||||||
|
resultCh: make(chan *types.Block),
|
||||||
|
remoteOp: make(chan *remoteOp),
|
||||||
|
}
|
||||||
|
go ethash.remote(ethash.resultCh)
|
||||||
|
runtime.SetFinalizer(ethash, (*Ethash).close)
|
||||||
|
return ethash
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts
|
// NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts
|
||||||
|
|
@ -606,14 +629,36 @@ func (ethash *Ethash) SetThreads(threads int) {
|
||||||
|
|
||||||
// Hashrate implements PoW, returning the measured rate of the search invocations
|
// Hashrate implements PoW, returning the measured rate of the search invocations
|
||||||
// per second over the last minute.
|
// per second over the last minute.
|
||||||
|
// Note the returned hashrate includes local hashrate, but also includes the total
|
||||||
|
// hashrate of all remote miner.
|
||||||
func (ethash *Ethash) Hashrate() float64 {
|
func (ethash *Ethash) Hashrate() float64 {
|
||||||
return ethash.hashrate.Rate1()
|
var (
|
||||||
|
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
|
||||||
|
|
||||||
|
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{{
|
return []rpc.API{{
|
||||||
Namespace: "ethash",
|
Namespace: "eth",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Service: &API{ethash},
|
Service: &API{ethash},
|
||||||
Public: true,
|
Public: true,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -69,7 +71,7 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) {
|
||||||
const wiggle = 4 * epochLength
|
const wiggle = 4 * epochLength
|
||||||
r := rand.New(rand.NewSource(int64(workerIndex)))
|
r := rand.New(rand.NewSource(int64(workerIndex)))
|
||||||
for epoch := 0; epoch < epochs; epoch++ {
|
for epoch := 0; epoch < epochs; epoch++ {
|
||||||
block := int64(epoch)*epochLength - wiggle/2 + r.Int63n(wiggle)
|
block := int64(epoch) * epochLength - wiggle / 2 + r.Int63n(wiggle)
|
||||||
if block < 0 {
|
if block < 0 {
|
||||||
block = 0
|
block = 0
|
||||||
}
|
}
|
||||||
|
|
@ -77,3 +79,47 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) {
|
||||||
e.VerifySeal(nil, head)
|
e.VerifySeal(nil, head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoteSealer(t *testing.T) {
|
||||||
|
ethash := NewTester()
|
||||||
|
api := &API{ethash}
|
||||||
|
if _, err := api.GetWork(); err != errNoMineWork {
|
||||||
|
t.Error("expected to return an error indicate there is no mining work")
|
||||||
|
}
|
||||||
|
|
||||||
|
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
||||||
|
block := types.NewBlockWithHeader(head)
|
||||||
|
// Push new work
|
||||||
|
ethash.Seal(nil, block, nil)
|
||||||
|
|
||||||
|
var (
|
||||||
|
work [3]string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() {
|
||||||
|
t.Error("expected to return a mining work has same hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
|
||||||
|
t.Error("expected to return false when submit a fake solution")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashRate(t *testing.T) {
|
||||||
|
var (
|
||||||
|
ethash = NewTester()
|
||||||
|
api = &API{ethash}
|
||||||
|
hashrate = []hexutil.Uint64{100, 200, 300}
|
||||||
|
expect uint64
|
||||||
|
ids = []common.Hash{common.HexToHash("a"), common.HexToHash("b"), common.HexToHash("c")}
|
||||||
|
)
|
||||||
|
for i := 0; i < len(hashrate); i += 1 {
|
||||||
|
if res := api.SubmitHashRate(hashrate[i], ids[i]); !res {
|
||||||
|
t.Error("remote miner submit hashrate failed")
|
||||||
|
}
|
||||||
|
expect += uint64(hashrate[i])
|
||||||
|
}
|
||||||
|
if tot := ethash.Hashrate(); tot != float64(expect) {
|
||||||
|
t.Error("expect total hashrate should be same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,14 @@ package ethash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"errors"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -32,9 +33,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNoSealWork = errors.New("No work available yet, don't panic.")
|
errNoMineWork = errors.New("No mining work available yet, don't panic.")
|
||||||
errInvalidSealResult = errors.New("Invalid proof-of-work solution.")
|
errInvalidSealResult = errors.New("Invalid or stale proof-of-work solution.")
|
||||||
errNonDefinedRemoteOp = errors.New("Non-defined remote mining operation.")
|
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
|
||||||
|
|
@ -72,15 +73,6 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
|
||||||
threads = 0 // Allows disabling local mining without extra logic around local/remote
|
threads = 0 // Allows disabling local mining without extra logic around local/remote
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear up result channel before new round mining start
|
|
||||||
for empty := false; !empty; {
|
|
||||||
select {
|
|
||||||
case <-ethash.resultCh:
|
|
||||||
default:
|
|
||||||
empty = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push new work to remote sealer
|
// Push new work to remote sealer
|
||||||
ethash.workCh <- block
|
ethash.workCh <- block
|
||||||
|
|
||||||
|
|
@ -175,17 +167,18 @@ search:
|
||||||
func (ethash *Ethash) remote(resultCh chan *types.Block) {
|
func (ethash *Ethash) remote(resultCh chan *types.Block) {
|
||||||
var (
|
var (
|
||||||
works = make(map[common.Hash]*types.Block)
|
works = make(map[common.Hash]*types.Block)
|
||||||
|
hashrate = make(map[common.Hash]hashrate)
|
||||||
currentWork *types.Block
|
currentWork *types.Block
|
||||||
)
|
)
|
||||||
|
|
||||||
// makeWork returns a work package for external sealer. 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
|
||||||
makeWork := 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, errNoSealWork
|
return res, errNoMineWork
|
||||||
}
|
}
|
||||||
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()
|
||||||
|
|
@ -199,10 +192,10 @@ func (ethash *Ethash) remote(resultCh chan *types.Block) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyWork 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 work pending).
|
||||||
verifyWork := func(result sealResult) bool {
|
submitWork := func(result mineResult) bool {
|
||||||
// Make sure the work submitted is present
|
// Make sure the work submitted is present
|
||||||
block := works[result.hash]
|
block := works[result.hash]
|
||||||
if block == nil {
|
if block == nil {
|
||||||
|
|
@ -220,42 +213,65 @@ func (ethash *Ethash) remote(resultCh chan *types.Block) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solutions seems to be valid, return to the miner and notify acceptance
|
// Solutions seems to be valid, return to the miner and notify acceptance
|
||||||
resultCh <- block.WithSeal(header)
|
select {
|
||||||
|
case resultCh <- block.WithSeal(header):
|
||||||
delete(works, result.hash)
|
delete(works, result.hash)
|
||||||
return true
|
return true
|
||||||
|
default:
|
||||||
|
log.Info("Work submitted is stale", "hash", result.hash)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
running:
|
running:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case block := <-ethash.workCh:
|
case block := <-ethash.workCh:
|
||||||
if block.ParentHash() != currentWork.ParentHash() {
|
if currentWork != nil && block.ParentHash() != currentWork.ParentHash() {
|
||||||
// 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.
|
||||||
currentWork = block
|
currentWork = block
|
||||||
|
|
||||||
case op := <-ethash.remoteOp:
|
case op := <-ethash.remoteOp:
|
||||||
switch op.typ {
|
switch op.typ {
|
||||||
case getWork:
|
case opGetWork:
|
||||||
// Push current mining work to return channel
|
// Return current mining work to remote miner
|
||||||
work, err := makeWork()
|
work, err := getWork()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
op.errCh <- err
|
op.errCh <- err
|
||||||
} else {
|
} else {
|
||||||
op.workCh <- work
|
|
||||||
close(op.errCh)
|
close(op.errCh)
|
||||||
|
op.workCh <- work
|
||||||
}
|
}
|
||||||
case submitWork:
|
case opSubmitWork:
|
||||||
// Verify submitted PoW solution based on maintained mining blocks
|
// Verify submitted PoW solution based on maintained mining blocks
|
||||||
if verifyWork(op.result) {
|
if submitWork(op.result) {
|
||||||
close(op.errCh)
|
close(op.errCh)
|
||||||
} else {
|
} else {
|
||||||
op.errCh <- errInvalidSealResult
|
op.errCh <- errInvalidSealResult
|
||||||
}
|
}
|
||||||
|
case opHashrate:
|
||||||
|
// This case is used by remote miner hashrate operation
|
||||||
|
if op.rateOp != nil {
|
||||||
|
op.rateOp(hashrate)
|
||||||
|
close(op.errCh)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
op.errCh <- errNonDefinedRemoteOp
|
op.errCh <- errUndefinedRemoteOp
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
// Clear stale submited hashrate
|
||||||
|
for id, rate := range hashrate {
|
||||||
|
if time.Since(rate.ping) > 10*time.Second {
|
||||||
|
delete(hashrate, id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ethash.exitCh:
|
case <-ethash.exitCh:
|
||||||
|
|
|
||||||
38
eth/api.go
38
eth/api.go
|
|
@ -34,7 +34,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
@ -71,15 +70,11 @@ func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 {
|
||||||
// It offers only methods that operate on data that pose no security risk when it is publicly accessible.
|
// It offers only methods that operate on data that pose no security risk when it is publicly accessible.
|
||||||
type PublicMinerAPI struct {
|
type PublicMinerAPI struct {
|
||||||
e *Ethereum
|
e *Ethereum
|
||||||
agent *miner.RemoteAgent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicMinerAPI create a new PublicMinerAPI instance.
|
// NewPublicMinerAPI create a new PublicMinerAPI instance.
|
||||||
func NewPublicMinerAPI(e *Ethereum) *PublicMinerAPI {
|
func NewPublicMinerAPI(e *Ethereum) *PublicMinerAPI {
|
||||||
agent := miner.NewRemoteAgent(e.BlockChain(), e.Engine())
|
return &PublicMinerAPI{e}
|
||||||
e.Miner().Register(agent)
|
|
||||||
|
|
||||||
return &PublicMinerAPI{e, agent}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mining returns an indication if this node is currently mining.
|
// Mining returns an indication if this node is currently mining.
|
||||||
|
|
@ -87,37 +82,6 @@ func (api *PublicMinerAPI) Mining() bool {
|
||||||
return api.e.IsMining()
|
return api.e.IsMining()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitWork can be used by external miner to submit their POW solution. It returns an indication if the work was
|
|
||||||
// accepted. Note, this is not an indication if the provided work was valid!
|
|
||||||
func (api *PublicMinerAPI) SubmitWork(nonce types.BlockNonce, solution, digest common.Hash) bool {
|
|
||||||
return api.agent.SubmitWork(nonce, digest, solution)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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[1], 32 bytes hex encoded seed hash used for DAG
|
|
||||||
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
|
||||||
func (api *PublicMinerAPI) GetWork() ([3]string, error) {
|
|
||||||
if !api.e.IsMining() {
|
|
||||||
if err := api.e.StartMining(false); err != nil {
|
|
||||||
return [3]string{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
work, err := api.agent.GetWork()
|
|
||||||
if err != nil {
|
|
||||||
return work, fmt.Errorf("mining not ready: %v", err)
|
|
||||||
}
|
|
||||||
return work, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitHashrate can be used for remote miners to submit their hash rate. This enables the node to report the combined
|
|
||||||
// hash rate of all miners which submit work through this node. It accepts the miner hash rate and an identifier which
|
|
||||||
// must be unique between nodes.
|
|
||||||
func (api *PublicMinerAPI) SubmitHashrate(hashrate hexutil.Uint64, id common.Hash) bool {
|
|
||||||
api.agent.SubmitHashrate(id, uint64(hashrate))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateMinerAPI provides private RPC methods to control the miner.
|
// PrivateMinerAPI provides private RPC methods to control the miner.
|
||||||
// These methods can be abused by external users and must be considered insecure for use by untrusted users.
|
// These methods can be abused by external users and must be considered insecure for use by untrusted users.
|
||||||
type PrivateMinerAPI struct {
|
type PrivateMinerAPI struct {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package miner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -110,10 +109,3 @@ func (self *CpuAgent) mine(work *Work, stop <-chan struct{}) {
|
||||||
self.returnCh <- nil
|
self.returnCh <- nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CpuAgent) GetHashRate() int64 {
|
|
||||||
if pow, ok := self.engine.(consensus.PoW); ok {
|
|
||||||
return int64(pow.Hashrate())
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -143,14 +143,6 @@ func (self *Miner) HashRate() (tot int64) {
|
||||||
if pow, ok := self.engine.(consensus.PoW); ok {
|
if pow, ok := self.engine.(consensus.PoW); ok {
|
||||||
tot += int64(pow.Hashrate())
|
tot += int64(pow.Hashrate())
|
||||||
}
|
}
|
||||||
// do we care this might race? is it worth we're rewriting some
|
|
||||||
// aspects of the worker/locking up agents so we can get an accurate
|
|
||||||
// hashrate?
|
|
||||||
for agent := range self.worker.agents {
|
|
||||||
if _, ok := agent.(*CpuAgent); !ok {
|
|
||||||
tot += agent.GetHashRate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// 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/>.
|
|
||||||
|
|
||||||
package miner
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type hashrate struct {
|
|
||||||
ping time.Time
|
|
||||||
rate uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoteAgent struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
quitCh chan struct{}
|
|
||||||
workCh chan *Work
|
|
||||||
returnCh chan<- *Result
|
|
||||||
|
|
||||||
chain consensus.ChainReader
|
|
||||||
engine consensus.Engine
|
|
||||||
currentWork *Work
|
|
||||||
work map[common.Hash]*Work
|
|
||||||
|
|
||||||
hashrateMu sync.RWMutex
|
|
||||||
hashrate map[common.Hash]hashrate
|
|
||||||
|
|
||||||
running int32 // running indicates whether the agent is active. Call atomically
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRemoteAgent(chain consensus.ChainReader, engine consensus.Engine) *RemoteAgent {
|
|
||||||
return &RemoteAgent{
|
|
||||||
chain: chain,
|
|
||||||
engine: engine,
|
|
||||||
work: make(map[common.Hash]*Work),
|
|
||||||
hashrate: make(map[common.Hash]hashrate),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) SubmitHashrate(id common.Hash, rate uint64) {
|
|
||||||
a.hashrateMu.Lock()
|
|
||||||
defer a.hashrateMu.Unlock()
|
|
||||||
|
|
||||||
a.hashrate[id] = hashrate{time.Now(), rate}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) Work() chan<- *Work {
|
|
||||||
return a.workCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) SetReturnCh(returnCh chan<- *Result) {
|
|
||||||
a.returnCh = returnCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) Start() {
|
|
||||||
if !atomic.CompareAndSwapInt32(&a.running, 0, 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
a.quitCh = make(chan struct{})
|
|
||||||
a.workCh = make(chan *Work, 1)
|
|
||||||
go a.loop(a.workCh, a.quitCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) Stop() {
|
|
||||||
if !atomic.CompareAndSwapInt32(&a.running, 1, 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(a.quitCh)
|
|
||||||
close(a.workCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHashRate returns the accumulated hashrate of all identifier combined
|
|
||||||
func (a *RemoteAgent) GetHashRate() (tot int64) {
|
|
||||||
a.hashrateMu.RLock()
|
|
||||||
defer a.hashrateMu.RUnlock()
|
|
||||||
|
|
||||||
// this could overflow
|
|
||||||
for _, hashrate := range a.hashrate {
|
|
||||||
tot += int64(hashrate.rate)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RemoteAgent) GetWork() ([3]string, error) {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
var res [3]string
|
|
||||||
|
|
||||||
if a.currentWork != nil {
|
|
||||||
block := a.currentWork.Block
|
|
||||||
|
|
||||||
res[0] = block.HashNoNonce().Hex()
|
|
||||||
seedHash := ethash.SeedHash(block.NumberU64())
|
|
||||||
res[1] = common.BytesToHash(seedHash).Hex()
|
|
||||||
// Calculate the "target" to be returned to the external miner
|
|
||||||
n := big.NewInt(1)
|
|
||||||
n.Lsh(n, 255)
|
|
||||||
n.Div(n, block.Difficulty())
|
|
||||||
n.Lsh(n, 1)
|
|
||||||
res[2] = common.BytesToHash(n.Bytes()).Hex()
|
|
||||||
|
|
||||||
a.work[block.HashNoNonce()] = a.currentWork
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
return res, errors.New("No work available yet, don't panic.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitWork tries to inject a pow solution into the remote agent, returning
|
|
||||||
// whether the solution was accepted or not (not can be both a bad pow as well as
|
|
||||||
// any other error, like no work pending).
|
|
||||||
func (a *RemoteAgent) SubmitWork(nonce types.BlockNonce, mixDigest, hash common.Hash) bool {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
// Make sure the work submitted is present
|
|
||||||
work := a.work[hash]
|
|
||||||
if work == nil {
|
|
||||||
log.Info("Work submitted but none pending", "hash", hash)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Make sure the Engine solutions is indeed valid
|
|
||||||
result := work.Block.Header()
|
|
||||||
result.Nonce = nonce
|
|
||||||
result.MixDigest = mixDigest
|
|
||||||
|
|
||||||
if err := a.engine.VerifySeal(a.chain, result); err != nil {
|
|
||||||
log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
block := work.Block.WithSeal(result)
|
|
||||||
|
|
||||||
// Solutions seems to be valid, return to the miner and notify acceptance
|
|
||||||
a.returnCh <- &Result{work, block}
|
|
||||||
delete(a.work, hash)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop monitors mining events on the work and quit channels, updating the internal
|
|
||||||
// state of the remote miner until a termination is requested.
|
|
||||||
//
|
|
||||||
// Note, the reason the work and quit channels are passed as parameters is because
|
|
||||||
// RemoteAgent.Start() constantly recreates these channels, so the loop code cannot
|
|
||||||
// assume data stability in these member fields.
|
|
||||||
func (a *RemoteAgent) loop(workCh chan *Work, quitCh chan struct{}) {
|
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quitCh:
|
|
||||||
return
|
|
||||||
case work := <-workCh:
|
|
||||||
a.mu.Lock()
|
|
||||||
a.currentWork = work
|
|
||||||
a.mu.Unlock()
|
|
||||||
case <-ticker.C:
|
|
||||||
// cleanup
|
|
||||||
a.mu.Lock()
|
|
||||||
for hash, work := range a.work {
|
|
||||||
if time.Since(work.createdAt) > 7*(12*time.Second) {
|
|
||||||
delete(a.work, hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.mu.Unlock()
|
|
||||||
|
|
||||||
a.hashrateMu.Lock()
|
|
||||||
for id, hashrate := range a.hashrate {
|
|
||||||
if time.Since(hashrate.ping) > 10*time.Second {
|
|
||||||
delete(a.hashrate, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.hashrateMu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -57,7 +57,6 @@ type Agent interface {
|
||||||
SetReturnCh(chan<- *Result)
|
SetReturnCh(chan<- *Result)
|
||||||
Stop()
|
Stop()
|
||||||
Start()
|
Start()
|
||||||
GetHashRate() int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Work is the workers current environment and holds
|
// Work is the workers current environment and holds
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue