cmd/geth, consensus/ethash: add support for --miner.notify.full flag (#22558)
The PR implements the --miner.notify.full flag that enables full pending block notifications. When this flag is used, the block notifications sent to mining endpoints contain the complete block header JSON instead of a work package array. Co-authored-by: AlexSSD7 <alexandersadovskyi7@protonmail.com> Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
@ -726,10 +726,14 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) {
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
pend.Add(1)
|
||||
|
||||
go func(idx int) {
|
||||
defer pend.Done()
|
||||
ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil}, nil, false)
|
||||
|
||||
config := Config{
|
||||
CacheDir: cachedir,
|
||||
CachesOnDisk: 1,
|
||||
}
|
||||
ethash := New(config, nil, false)
|
||||
defer ethash.Close()
|
||||
if err := ethash.verifySeal(nil, block.Header(), false); err != nil {
|
||||
t.Errorf("proc %d: block verification failed: %v", idx, err)
|
||||
|
@ -48,7 +48,7 @@ var (
|
||||
two256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
|
||||
|
||||
// sharedEthash is a full instance that can be shared between multiple users.
|
||||
sharedEthash = New(Config{"", 3, 0, false, "", 1, 0, false, ModeNormal, nil}, nil, false)
|
||||
sharedEthash *Ethash
|
||||
|
||||
// algorithmRevision is the data structure version used for file naming.
|
||||
algorithmRevision = 23
|
||||
@ -57,6 +57,15 @@ var (
|
||||
dumpMagic = []uint32{0xbaddcafe, 0xfee1dead}
|
||||
)
|
||||
|
||||
func init() {
|
||||
sharedConfig := Config{
|
||||
PowMode: ModeNormal,
|
||||
CachesInMem: 3,
|
||||
DatasetsInMem: 1,
|
||||
}
|
||||
sharedEthash = New(sharedConfig, nil, false)
|
||||
}
|
||||
|
||||
// isLittleEndian returns whether the local system is running in little or big
|
||||
// endian byte order.
|
||||
func isLittleEndian() bool {
|
||||
@ -411,6 +420,10 @@ type Config struct {
|
||||
DatasetsLockMmap bool
|
||||
PowMode Mode
|
||||
|
||||
// When set, notifications sent by the remote sealer will
|
||||
// be block header JSON objects instead of work package arrays.
|
||||
NotifyFull bool
|
||||
|
||||
Log log.Logger `toml:"-"`
|
||||
}
|
||||
|
||||
@ -462,6 +475,9 @@ func New(config Config, notify []string, noverify bool) *Ethash {
|
||||
update: make(chan struct{}),
|
||||
hashrate: metrics.NewMeterForced(),
|
||||
}
|
||||
if config.PowMode == ModeShared {
|
||||
ethash.shared = sharedEthash
|
||||
}
|
||||
ethash.remote = startRemoteSealer(ethash, notify, noverify)
|
||||
return ethash
|
||||
}
|
||||
@ -469,15 +485,7 @@ func New(config Config, notify []string, noverify bool) *Ethash {
|
||||
// NewTester creates a small sized ethash PoW scheme useful only for testing
|
||||
// purposes.
|
||||
func NewTester(notify []string, noverify bool) *Ethash {
|
||||
ethash := &Ethash{
|
||||
config: Config{PowMode: ModeTest, Log: log.Root()},
|
||||
caches: newlru("cache", 1, newCache),
|
||||
datasets: newlru("dataset", 1, newDataset),
|
||||
update: make(chan struct{}),
|
||||
hashrate: metrics.NewMeterForced(),
|
||||
}
|
||||
ethash.remote = startRemoteSealer(ethash, notify, noverify)
|
||||
return ethash
|
||||
return New(Config{PowMode: ModeTest}, notify, noverify)
|
||||
}
|
||||
|
||||
// NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts
|
||||
|
@ -62,7 +62,14 @@ func TestCacheFileEvict(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
e := New(Config{CachesInMem: 3, CachesOnDisk: 10, CacheDir: tmpdir, PowMode: ModeTest}, nil, false)
|
||||
|
||||
config := Config{
|
||||
CachesInMem: 3,
|
||||
CachesOnDisk: 10,
|
||||
CacheDir: tmpdir,
|
||||
PowMode: ModeTest,
|
||||
}
|
||||
e := New(config, nil, false)
|
||||
defer e.Close()
|
||||
|
||||
workers := 8
|
||||
|
@ -358,7 +358,16 @@ func (s *remoteSealer) makeWork(block *types.Block) {
|
||||
// new work to be processed.
|
||||
func (s *remoteSealer) notifyWork() {
|
||||
work := s.currentWork
|
||||
blob, _ := json.Marshal(work)
|
||||
|
||||
// Encode the JSON payload of the notification. When NotifyFull is set,
|
||||
// this is the complete block header, otherwise it is a JSON array.
|
||||
var blob []byte
|
||||
if s.ethash.config.NotifyFull {
|
||||
blob, _ = json.Marshal(s.currentBlock.Header())
|
||||
} else {
|
||||
blob, _ = json.Marshal(work)
|
||||
}
|
||||
|
||||
s.reqWG.Add(len(s.notifyURLs))
|
||||
for _, url := range s.notifyURLs {
|
||||
go s.sendNotification(s.notifyCtx, url, blob, work)
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -74,6 +75,50 @@ func TestRemoteNotify(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests whether remote HTTP servers are correctly notified of new work. (Full pending block body / --miner.notify.full)
|
||||
func TestRemoteNotifyFull(t *testing.T) {
|
||||
// Start a simple web server to capture notifications.
|
||||
sink := make(chan map[string]interface{})
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
blob, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("failed to read miner notification: %v", err)
|
||||
}
|
||||
var work map[string]interface{}
|
||||
if err := json.Unmarshal(blob, &work); err != nil {
|
||||
t.Errorf("failed to unmarshal miner notification: %v", err)
|
||||
}
|
||||
sink <- work
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Create the custom ethash engine.
|
||||
config := Config{
|
||||
PowMode: ModeTest,
|
||||
NotifyFull: true,
|
||||
Log: testlog.Logger(t, log.LvlWarn),
|
||||
}
|
||||
ethash := New(config, []string{server.URL}, false)
|
||||
defer ethash.Close()
|
||||
|
||||
// Stream a work task and ensure the notification bubbles out.
|
||||
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
||||
block := types.NewBlockWithHeader(header)
|
||||
|
||||
ethash.Seal(nil, block, nil, nil)
|
||||
select {
|
||||
case work := <-sink:
|
||||
if want := "0x" + strconv.FormatUint(header.Number.Uint64(), 16); work["number"] != want {
|
||||
t.Errorf("pending block number mismatch: have %v, want %v", work["number"], want)
|
||||
}
|
||||
if want := "0x" + header.Difficulty.Text(16); work["difficulty"] != want {
|
||||
t.Errorf("pending block difficulty mismatch: have %s, want %s", work["difficulty"], want)
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("notification timed out")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that pushing work packages fast to the miner doesn't cause any data race
|
||||
// issues in the notifications.
|
||||
func TestRemoteMultiNotify(t *testing.T) {
|
||||
@ -119,6 +164,55 @@ func TestRemoteMultiNotify(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that pushing work packages fast to the miner doesn't cause any data race
|
||||
// issues in the notifications. Full pending block body / --miner.notify.full)
|
||||
func TestRemoteMultiNotifyFull(t *testing.T) {
|
||||
// Start a simple web server to capture notifications.
|
||||
sink := make(chan map[string]interface{}, 64)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
blob, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("failed to read miner notification: %v", err)
|
||||
}
|
||||
var work map[string]interface{}
|
||||
if err := json.Unmarshal(blob, &work); err != nil {
|
||||
t.Errorf("failed to unmarshal miner notification: %v", err)
|
||||
}
|
||||
sink <- work
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Create the custom ethash engine.
|
||||
config := Config{
|
||||
PowMode: ModeTest,
|
||||
NotifyFull: true,
|
||||
Log: testlog.Logger(t, log.LvlWarn),
|
||||
}
|
||||
ethash := New(config, []string{server.URL}, false)
|
||||
defer ethash.Close()
|
||||
|
||||
// Provide a results reader.
|
||||
// Otherwise the unread results will be logged asynchronously
|
||||
// and this can happen after the test is finished, causing a panic.
|
||||
results := make(chan *types.Block, cap(sink))
|
||||
|
||||
// Stream a lot of work task and ensure all the notifications bubble out.
|
||||
for i := 0; i < cap(sink); i++ {
|
||||
header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
|
||||
block := types.NewBlockWithHeader(header)
|
||||
ethash.Seal(nil, block, results, nil)
|
||||
}
|
||||
|
||||
for i := 0; i < cap(sink); i++ {
|
||||
select {
|
||||
case <-sink:
|
||||
<-results
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("notification %d timed out", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests whether stale solutions are correctly processed.
|
||||
func TestStaleSubmission(t *testing.T) {
|
||||
ethash := NewTester(nil, true)
|
||||
|
Reference in New Issue
Block a user