Skip to content

Commit cae6b55

Browse files
fjlAlexSSD7holiman
authored
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 <[email protected]> Co-authored-by: Martin Holst Swende <[email protected]>
1 parent 9557271 commit cae6b55

File tree

11 files changed

+171
-39
lines changed

11 files changed

+171
-39
lines changed

cmd/geth/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ var (
152152
utils.GpoMaxGasPriceFlag,
153153
utils.EWASMInterpreterFlag,
154154
utils.EVMInterpreterFlag,
155+
utils.MinerNotifyFullFlag,
155156
configFileFlag,
156157
}
157158

cmd/geth/usage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
180180
utils.MiningEnabledFlag,
181181
utils.MinerThreadsFlag,
182182
utils.MinerNotifyFlag,
183+
utils.MinerNotifyFullFlag,
183184
utils.MinerGasPriceFlag,
184185
utils.MinerGasTargetFlag,
185186
utils.MinerGasLimitFlag,

cmd/utils/flags.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,10 @@ var (
427427
Name: "miner.notify",
428428
Usage: "Comma separated HTTP URL list to notify of new work packages",
429429
}
430+
MinerNotifyFullFlag = cli.BoolFlag{
431+
Name: "miner.notify.full",
432+
Usage: "Notify with pending block headers instead of work packages",
433+
}
430434
MinerGasTargetFlag = cli.Uint64Flag{
431435
Name: "miner.gastarget",
432436
Usage: "Target gas floor for mined blocks",
@@ -1359,6 +1363,7 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
13591363
if ctx.GlobalIsSet(MinerNotifyFlag.Name) {
13601364
cfg.Notify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
13611365
}
1366+
cfg.NotifyFull = ctx.GlobalBool(MinerNotifyFullFlag.Name)
13621367
if ctx.GlobalIsSet(MinerExtraDataFlag.Name) {
13631368
cfg.ExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name))
13641369
}

consensus/ethash/algorithm_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,10 +726,14 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) {
726726

727727
for i := 0; i < 3; i++ {
728728
pend.Add(1)
729-
730729
go func(idx int) {
731730
defer pend.Done()
732-
ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil}, nil, false)
731+
732+
config := Config{
733+
CacheDir: cachedir,
734+
CachesOnDisk: 1,
735+
}
736+
ethash := New(config, nil, false)
733737
defer ethash.Close()
734738
if err := ethash.verifySeal(nil, block.Header(), false); err != nil {
735739
t.Errorf("proc %d: block verification failed: %v", idx, err)

consensus/ethash/ethash.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ var (
4848
two256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
4949

5050
// sharedEthash is a full instance that can be shared between multiple users.
51-
sharedEthash = New(Config{"", 3, 0, false, "", 1, 0, false, ModeNormal, nil}, nil, false)
51+
sharedEthash *Ethash
5252

5353
// algorithmRevision is the data structure version used for file naming.
5454
algorithmRevision = 23
@@ -57,6 +57,15 @@ var (
5757
dumpMagic = []uint32{0xbaddcafe, 0xfee1dead}
5858
)
5959

60+
func init() {
61+
sharedConfig := Config{
62+
PowMode: ModeNormal,
63+
CachesInMem: 3,
64+
DatasetsInMem: 1,
65+
}
66+
sharedEthash = New(sharedConfig, nil, false)
67+
}
68+
6069
// isLittleEndian returns whether the local system is running in little or big
6170
// endian byte order.
6271
func isLittleEndian() bool {
@@ -411,6 +420,10 @@ type Config struct {
411420
DatasetsLockMmap bool
412421
PowMode Mode
413422

423+
// When set, notifications sent by the remote sealer will
424+
// be block header JSON objects instead of work package arrays.
425+
NotifyFull bool
426+
414427
Log log.Logger `toml:"-"`
415428
}
416429

@@ -462,22 +475,17 @@ func New(config Config, notify []string, noverify bool) *Ethash {
462475
update: make(chan struct{}),
463476
hashrate: metrics.NewMeterForced(),
464477
}
478+
if config.PowMode == ModeShared {
479+
ethash.shared = sharedEthash
480+
}
465481
ethash.remote = startRemoteSealer(ethash, notify, noverify)
466482
return ethash
467483
}
468484

469485
// NewTester creates a small sized ethash PoW scheme useful only for testing
470486
// purposes.
471487
func NewTester(notify []string, noverify bool) *Ethash {
472-
ethash := &Ethash{
473-
config: Config{PowMode: ModeTest, Log: log.Root()},
474-
caches: newlru("cache", 1, newCache),
475-
datasets: newlru("dataset", 1, newDataset),
476-
update: make(chan struct{}),
477-
hashrate: metrics.NewMeterForced(),
478-
}
479-
ethash.remote = startRemoteSealer(ethash, notify, noverify)
480-
return ethash
488+
return New(Config{PowMode: ModeTest}, notify, noverify)
481489
}
482490

483491
// NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts

consensus/ethash/ethash_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,14 @@ func TestCacheFileEvict(t *testing.T) {
6262
t.Fatal(err)
6363
}
6464
defer os.RemoveAll(tmpdir)
65-
e := New(Config{CachesInMem: 3, CachesOnDisk: 10, CacheDir: tmpdir, PowMode: ModeTest}, nil, false)
65+
66+
config := Config{
67+
CachesInMem: 3,
68+
CachesOnDisk: 10,
69+
CacheDir: tmpdir,
70+
PowMode: ModeTest,
71+
}
72+
e := New(config, nil, false)
6673
defer e.Close()
6774

6875
workers := 8

consensus/ethash/sealer.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,16 @@ func (s *remoteSealer) makeWork(block *types.Block) {
358358
// new work to be processed.
359359
func (s *remoteSealer) notifyWork() {
360360
work := s.currentWork
361-
blob, _ := json.Marshal(work)
361+
362+
// Encode the JSON payload of the notification. When NotifyFull is set,
363+
// this is the complete block header, otherwise it is a JSON array.
364+
var blob []byte
365+
if s.ethash.config.NotifyFull {
366+
blob, _ = json.Marshal(s.currentBlock.Header())
367+
} else {
368+
blob, _ = json.Marshal(work)
369+
}
370+
362371
s.reqWG.Add(len(s.notifyURLs))
363372
for _, url := range s.notifyURLs {
364373
go s.sendNotification(s.notifyCtx, url, blob, work)

consensus/ethash/sealer_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"math/big"
2323
"net/http"
2424
"net/http/httptest"
25+
"strconv"
2526
"testing"
2627
"time"
2728

@@ -74,6 +75,50 @@ func TestRemoteNotify(t *testing.T) {
7475
}
7576
}
7677

78+
// Tests whether remote HTTP servers are correctly notified of new work. (Full pending block body / --miner.notify.full)
79+
func TestRemoteNotifyFull(t *testing.T) {
80+
// Start a simple web server to capture notifications.
81+
sink := make(chan map[string]interface{})
82+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
83+
blob, err := ioutil.ReadAll(req.Body)
84+
if err != nil {
85+
t.Errorf("failed to read miner notification: %v", err)
86+
}
87+
var work map[string]interface{}
88+
if err := json.Unmarshal(blob, &work); err != nil {
89+
t.Errorf("failed to unmarshal miner notification: %v", err)
90+
}
91+
sink <- work
92+
}))
93+
defer server.Close()
94+
95+
// Create the custom ethash engine.
96+
config := Config{
97+
PowMode: ModeTest,
98+
NotifyFull: true,
99+
Log: testlog.Logger(t, log.LvlWarn),
100+
}
101+
ethash := New(config, []string{server.URL}, false)
102+
defer ethash.Close()
103+
104+
// Stream a work task and ensure the notification bubbles out.
105+
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
106+
block := types.NewBlockWithHeader(header)
107+
108+
ethash.Seal(nil, block, nil, nil)
109+
select {
110+
case work := <-sink:
111+
if want := "0x" + strconv.FormatUint(header.Number.Uint64(), 16); work["number"] != want {
112+
t.Errorf("pending block number mismatch: have %v, want %v", work["number"], want)
113+
}
114+
if want := "0x" + header.Difficulty.Text(16); work["difficulty"] != want {
115+
t.Errorf("pending block difficulty mismatch: have %s, want %s", work["difficulty"], want)
116+
}
117+
case <-time.After(3 * time.Second):
118+
t.Fatalf("notification timed out")
119+
}
120+
}
121+
77122
// Tests that pushing work packages fast to the miner doesn't cause any data race
78123
// issues in the notifications.
79124
func TestRemoteMultiNotify(t *testing.T) {
@@ -119,6 +164,55 @@ func TestRemoteMultiNotify(t *testing.T) {
119164
}
120165
}
121166

167+
// Tests that pushing work packages fast to the miner doesn't cause any data race
168+
// issues in the notifications. Full pending block body / --miner.notify.full)
169+
func TestRemoteMultiNotifyFull(t *testing.T) {
170+
// Start a simple web server to capture notifications.
171+
sink := make(chan map[string]interface{}, 64)
172+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
173+
blob, err := ioutil.ReadAll(req.Body)
174+
if err != nil {
175+
t.Errorf("failed to read miner notification: %v", err)
176+
}
177+
var work map[string]interface{}
178+
if err := json.Unmarshal(blob, &work); err != nil {
179+
t.Errorf("failed to unmarshal miner notification: %v", err)
180+
}
181+
sink <- work
182+
}))
183+
defer server.Close()
184+
185+
// Create the custom ethash engine.
186+
config := Config{
187+
PowMode: ModeTest,
188+
NotifyFull: true,
189+
Log: testlog.Logger(t, log.LvlWarn),
190+
}
191+
ethash := New(config, []string{server.URL}, false)
192+
defer ethash.Close()
193+
194+
// Provide a results reader.
195+
// Otherwise the unread results will be logged asynchronously
196+
// and this can happen after the test is finished, causing a panic.
197+
results := make(chan *types.Block, cap(sink))
198+
199+
// Stream a lot of work task and ensure all the notifications bubble out.
200+
for i := 0; i < cap(sink); i++ {
201+
header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
202+
block := types.NewBlockWithHeader(header)
203+
ethash.Seal(nil, block, results, nil)
204+
}
205+
206+
for i := 0; i < cap(sink); i++ {
207+
select {
208+
case <-sink:
209+
<-results
210+
case <-time.After(10 * time.Second):
211+
t.Fatalf("notification %d timed out", i)
212+
}
213+
}
214+
}
215+
122216
// Tests whether stale solutions are correctly processed.
123217
func TestStaleSubmission(t *testing.T) {
124218
ethash := NewTester(nil, true)

eth/backend.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
121121
}
122122
log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
123123

124+
// Transfer mining-related config to the ethash config.
125+
ethashConfig := config.Ethash
126+
ethashConfig.NotifyFull = config.Miner.NotifyFull
127+
124128
// Assemble the Ethereum object
125129
chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false)
126130
if err != nil {
@@ -140,7 +144,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
140144
chainDb: chainDb,
141145
eventMux: stack.EventMux(),
142146
accountManager: stack.AccountManager(),
143-
engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb),
147+
engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &ethashConfig, config.Miner.Notify, config.Miner.Noverify, chainDb),
144148
closeBloomHandler: make(chan struct{}),
145149
networkID: config.NetworkId,
146150
gasPrice: config.Miner.GasPrice,

eth/ethconfig/config.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -213,25 +213,23 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co
213213
switch config.PowMode {
214214
case ethash.ModeFake:
215215
log.Warn("Ethash used in fake mode")
216-
return ethash.NewFaker()
217216
case ethash.ModeTest:
218217
log.Warn("Ethash used in test mode")
219-
return ethash.NewTester(nil, noverify)
220218
case ethash.ModeShared:
221219
log.Warn("Ethash used in shared mode")
222-
return ethash.NewShared()
223-
default:
224-
engine := ethash.New(ethash.Config{
225-
CacheDir: stack.ResolvePath(config.CacheDir),
226-
CachesInMem: config.CachesInMem,
227-
CachesOnDisk: config.CachesOnDisk,
228-
CachesLockMmap: config.CachesLockMmap,
229-
DatasetDir: config.DatasetDir,
230-
DatasetsInMem: config.DatasetsInMem,
231-
DatasetsOnDisk: config.DatasetsOnDisk,
232-
DatasetsLockMmap: config.DatasetsLockMmap,
233-
}, notify, noverify)
234-
engine.SetThreads(-1) // Disable CPU mining
235-
return engine
236220
}
221+
engine := ethash.New(ethash.Config{
222+
PowMode: config.PowMode,
223+
CacheDir: stack.ResolvePath(config.CacheDir),
224+
CachesInMem: config.CachesInMem,
225+
CachesOnDisk: config.CachesOnDisk,
226+
CachesLockMmap: config.CachesLockMmap,
227+
DatasetDir: config.DatasetDir,
228+
DatasetsInMem: config.DatasetsInMem,
229+
DatasetsOnDisk: config.DatasetsOnDisk,
230+
DatasetsLockMmap: config.DatasetsLockMmap,
231+
NotifyFull: config.NotifyFull,
232+
}, notify, noverify)
233+
engine.SetThreads(-1) // Disable CPU mining
234+
return engine
237235
}

0 commit comments

Comments
 (0)